add support libraries

This commit is contained in:
Hari Nair
2025-04-24 15:17:08 -04:00
parent f09f5818ec
commit fe409b64b8
59 changed files with 5013 additions and 0 deletions

1
support-libraries/.gitattributes vendored Normal file
View File

@@ -0,0 +1 @@
*.gz filter=lfs diff=lfs merge=lfs -text

6
support-libraries/.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
build/
dist/
install/
spice/src/
.DS_Store
*.swp

View File

@@ -0,0 +1,31 @@
# Supporting libraries for Terrasaur
The Terrasaur code is available at [GitHub](https://github.com/JHUAPL/Terrasaur.git). This directory contains supporting libraries needed to build Terrasaur.
* [GSL](https://www.gnu.org/software/gsl/)
* [SPICE](https://naif.jpl.nasa.gov)
* [VTK](https://vtk.org)
* [OpenCV](https://opencv.org/)
## Prerequisites
You may need to install the following packages with
your favorite package manager:
* [Apache Ant](https://ant.apache.org/) - needed for OpenCV Java support
* [SQLite](https://www.sqlite.org/index.html) - needed for lidar-optimize
## Build the packages
Each subdirectory has a build script which takes a single argument,
which is the installation location for the compiled libraries and
executables.
The `buildAll.bash` script will build and install the libraries and executables
to the desired location. For example, this would install everything in a
directory named for your system architecture (e.g. 3rd-party/Darwin_x86_64 on
an intel macOS system):
```
../buildAll.bash 3rd-party/$(uname -s)_$(uname -m)
```

View File

@@ -0,0 +1,52 @@
cmake_minimum_required(VERSION 3.17)
PROJECT(SBMT)
find_package(SQLite3 REQUIRED)
# built from install-altwg.sh
# find_package(GSL REQUIRED)
set(GSL_HOME $ENV{GSL_HOME})
set(VTK_DIR $ENV{VTK_HOME}/lib/cmake/vtk-9.0/)
find_package(VTK COMPONENTS vtkFiltersCore vtkIOLegacy vtkIOGeometry NO_MODULE)
set(SPICE_DIR $ENV{SPICE_HOME} CACHE PATH "Path to Spice")
add_definitions(-Wall)
# Print all environment variables
#get_cmake_property(_variableNames VARIABLES)
#list (SORT _variableNames)
#foreach (_variableName ${_variableNames})
# message(STATUS "${_variableName}=${${_variableName}}")
#endforeach()
include_directories(
${GSL_HOME}/include
${SPICE_DIR}/include
${CMAKE_SOURCE_DIR}
${CMAKE_SOURCE_DIR}/gravity
${CMAKE_SOURCE_DIR}/common
)
add_library(lidar
gravity/gravity-werner.cpp
gravity/gravity-cheng.cpp
gravity/gravity-point.h
gravity/platemodel.cpp
common/util.h
common/mathutil.h
strip-adjustment/optimizer.cpp
strip-adjustment/lidardata.cpp
strip-adjustment/closest-point-vtk.cpp
strip-adjustment/icp-gsl.cpp
strip-adjustment/optimize-gsl.cpp
)
target_link_libraries(lidar ${GSL_HOME}/lib/libgsl.a ${GSL_HOME}/lib/libgslcblas.a ${VTK_LIBRARIES} ${SPICE_DIR}/lib/cspice.a sqlite3)
add_executable(gravity gravity/gravity.cpp)
target_link_libraries(gravity lidar)
add_executable(lidar-optimize strip-adjustment/lidar-optimize.cpp)
target_link_libraries(lidar-optimize lidar)

View File

@@ -0,0 +1,188 @@
#ifndef MATHUTIL_H
#define MATHUTIL_H
#include <math.h>
#include "vtkTransform.h"
#include "vtkQuaternion.h"
// The following functions are copied directly from VTK
inline void Add(const double a[3], const double b[3], double c[3]) {
for (int i = 0; i < 3; ++i)
c[i] = a[i] + b[i];
}
inline void Subtract(const double a[3], const double b[3], double c[3]) {
for (int i = 0; i < 3; ++i)
c[i] = a[i] - b[i];
}
inline double Norm(const double x[3]) {
return sqrt( x[0] * x[0] + x[1] * x[1] + x[2] * x[2] );}
inline double Normalize(double x[3])
{
double den;
if ( ( den = Norm( x ) ) != 0.0 )
{
for (int i=0; i < 3; i++)
{
x[i] /= den;
}
}
return den;
}
inline void Cross(const double x[3], const double y[3], double z[3])
{
double Zx = x[1] * y[2] - x[2] * y[1];
double Zy = x[2] * y[0] - x[0] * y[2];
double Zz = x[0] * y[1] - x[1] * y[0];
z[0] = Zx; z[1] = Zy; z[2] = Zz;
}
inline double Dot(const double x[3], const double y[3]) {
return ( x[0] * y[0] + x[1] * y[1] + x[2] * y[2] );}
inline void Outer(const double x[3], const double y[3], double A[3][3]) {
for (int i=0; i < 3; i++)
for (int j=0; j < 3; j++)
A[i][j] = x[i] * y[j];
}
inline void MultiplyScalar(double a[3], double s) {
for (int i = 0; i < 3; ++i)
a[i] *= s;
}
inline void ComputeNormalDirection(double v1[3], double v2[3],
double v3[3], double n[3])
{
double ax, ay, az, bx, by, bz;
// order is important!!! maintain consistency with triangle vertex order
ax = v3[0] - v2[0]; ay = v3[1] - v2[1]; az = v3[2] - v2[2];
bx = v1[0] - v2[0]; by = v1[1] - v2[1]; bz = v1[2] - v2[2];
n[0] = (ay * bz - az * by);
n[1] = (az * bx - ax * bz);
n[2] = (ax * by - ay * bx);
}
inline void ComputeNormal(double v1[3], double v2[3],
double v3[3], double n[3])
{
double length;
ComputeNormalDirection(v1, v2, v3, n);
if ( (length = sqrt((n[0]*n[0] + n[1]*n[1] + n[2]*n[2]))) != 0.0 )
{
n[0] /= length;
n[1] /= length;
n[2] /= length;
}
}
inline void TriangleCenter(double p1[3], double p2[3],
double p3[3], double center[3])
{
center[0] = (p1[0]+p2[0]+p3[0]) / 3.0;
center[1] = (p1[1]+p2[1]+p3[1]) / 3.0;
center[2] = (p1[2]+p2[2]+p3[2]) / 3.0;
}
inline double Distance2BetweenPoints(const double x[3],
const double y[3])
{
return ( ( x[0] - y[0] ) * ( x[0] - y[0] )
+ ( x[1] - y[1] ) * ( x[1] - y[1] )
+ ( x[2] - y[2] ) * ( x[2] - y[2] ) );
}
inline double TriangleArea(double p1[3], double p2[3], double p3[3])
{
double a,b,c;
a = Distance2BetweenPoints(p1,p2);
b = Distance2BetweenPoints(p2,p3);
c = Distance2BetweenPoints(p3,p1);
return (0.25* sqrt(fabs(4.0*a*c - (a-b+c)*(a-b+c))));
}
template <class T>
void normalizeQuaternion(
const T quaternion[4],
T normalizedQuaternion[4])
{
const T& q0 = quaternion[0];
const T& q1 = quaternion[1];
const T& q2 = quaternion[2];
const T& q3 = quaternion[3];
// Copied from Rotation.java in Apache Commons Math
T inv = 1.0 / sqrt(q0 * q0 + q1 * q1 + q2 * q2 + q3 * q3);
normalizedQuaternion[0] = q0 * inv;
normalizedQuaternion[1] = q1 * inv;
normalizedQuaternion[2] = q2 * inv;
normalizedQuaternion[3] = q3 * inv;
}
template <class T1, class T2>
void applyRotationToVector(
const T1 vec[3],
const T2& q0,
const T2& q1,
const T2& q2,
const T2& q3,
T2 rotatedVector[3]
)
{
// Copied from Rotation.java in Apache Commons Math
const T1& x = vec[0];
const T1& y = vec[1];
const T1& z = vec[2];
T2 s = q1 * x + q2 * y + q3 * z;
rotatedVector[0] = 2.0 * (q0 * (x * q0 - (q2 * z - q3 * y)) + s * q1) - x;
rotatedVector[1] = 2.0 * (q0 * (y * q0 - (q3 * x - q1 * z)) + s * q2) - y;
rotatedVector[2] = 2.0 * (q0 * (z * q0 - (q1 * y - q2 * x)) + s * q3) - z;
}
template <class T1, class T2>
void transformPoint(
const T1 point[3],
const T1 centerOfRotation[3],
const T2 translation[3],
const T2 quaternion[4],
T2 transformedPoint[3]
)
{
const T2& q0 = quaternion[0];
const T2& q1 = quaternion[1];
const T2& q2 = quaternion[2];
const T2& q3 = quaternion[3];
T1 tmpPoint[3] = {point[0], point[1], point[2]};
// Translate source points to centroid
tmpPoint[0] -= centerOfRotation[0];
tmpPoint[1] -= centerOfRotation[1];
tmpPoint[2] -= centerOfRotation[2];
// Apply rotation to points
applyRotationToVector(tmpPoint, q0, q1, q2, q3, transformedPoint);
// Translate points back
transformedPoint[0] = transformedPoint[0] + centerOfRotation[0];
transformedPoint[1] = transformedPoint[1] + centerOfRotation[1];
transformedPoint[2] = transformedPoint[2] + centerOfRotation[2];
// Apply translation
transformedPoint[0] += translation[0];
transformedPoint[1] += translation[1];
transformedPoint[2] += translation[2];
}
#endif // MATHUTIL_H

View File

@@ -0,0 +1,70 @@
#ifndef __UTIL_H__
#define __UTIL_H__
#include <string>
#include <sstream>
#include <vector>
// The following 3 functions were adapted from
// http://stackoverflow.com/questions/479080/trim-is-not-part-of-the-standard-c-c-library?rq=1
static const std::string whiteSpaces( " \f\n\r\t\v" );
// Remove initial and trailing whitespace from string. Modifies string in-place
inline void trimRight( std::string& str,
const std::string& trimChars = whiteSpaces )
{
std::string::size_type pos = str.find_last_not_of( trimChars );
str.erase( pos + 1 );
}
inline void trimLeft( std::string& str,
const std::string& trimChars = whiteSpaces )
{
std::string::size_type pos = str.find_first_not_of( trimChars );
str.erase( 0, pos );
}
inline void trim( std::string& str,
const std::string& trimChars = whiteSpaces )
{
trimRight( str, trimChars );
trimLeft( str, trimChars );
}
inline bool endsWith(const std::string& fullString, const std::string& ending)
{
if (fullString.length() >= ending.length())
{
return (0 == fullString.compare (fullString.length() - ending.length(), ending.length(), ending));
}
else
{
return false;
}
}
// The following 2 functions were adapted from http://stackoverflow.com/questions/236129/how-to-split-a-string-in-c
inline void
split(const std::string &s, char delim, std::vector<std::string> &elems)
{
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim))
{
if (item.length() > 0)
elems.push_back(item);
}
}
inline std::vector<std::string>
split(const std::string &s, char delim = ' ')
{
std::vector<std::string> elems;
split(s, delim, elems);
return elems;
}
#endif

View File

@@ -0,0 +1,136 @@
------------
Introduction
------------
This README file describes how to build and run the gravity program
for computing the gravitational acceleration and potential of an
arbitrary triangular plate model. It implements the algorithm by
Werner and Scheeres[1] as well as the simpler, faster algorithm by
Andy Cheng[2].
------------------------
Compilation instructions
------------------------
Use CMake to compile. See CMake documentation for further details.
----------------------------
Gravity program instructions
----------------------------
This gravity executable takes various options as well as the path to
plate a model (in typical PDS format). It computes the potential,
acceleration, and acceleration magnitude and saves the values to files
in the current directory. You can choose between the Werner and Andy
Cheng's method. You can also choose between evaluating at the center
of each plate, or evaluating at the vertices of the shape model, or
averaging the values at the vertices at each plate center. You need to
provide the density (in g/cm^3) and rotation rate (in radians/per
second). It is assumed that the vertices are expressed in kilometers
and on output the potential is expressed in J/kg and the acceleration
in m/s^2. The gravity program takes the following options:
-d <value> Density of shape model in g/cm^3 (default is 1)
-r <value> Rotation rate of shape model in radians/sec (default is 0)
--werner Use the Werner algorithm for computing the gravity (this is the
default if neither --werner or --cheng option provided)
--cheng Use Andy Cheng's algorithm for computing the gravity (default is to
use Werner method if neither --werner or --cheng option provided)
--centers Evaluate gravity directly at the centers of plates (this is the default
if neither --centers or -vertices or --file option provided)
--average-vertices Evaluate gravity of each plate by averaging the gravity computed at the
3 vertices of the plate (default is to evaluate at centers)
--vertices Evaluate gravity directly at each vertex (default is to evaluate at centers)
When using this option, you must also add the --cheng option since singularities
occur at the vertices with the Werner algorithm.
--file <filename> Evaluate gravity at points specified in file (default is to evaluate
at centers)
--ref-potential <value> If the --file option is provided, then use this option to specify the reference
potential which is needed for calculating elevation. This option is ignored if
--file is not provided. If --file is provided but --ref-potential is not
provided then no elevation data is saved out.
--columns <int,int,int> If --file is provided, then this options controls which columns of the file are
assumed to contain the x, y, and z coordinates of the points. By default, columns
0, 1, and 2 are read. If you wanted, say, columns 3, 4, and 5 instead you would
include this option as for example: --columns 3,4,5. Note that they are separated
by commas (no spaces) and are zero based. If --file is not provided, then this
option is ignored.
--start-index <value>
--end-index <value> use these 2 options to specify a range of plates or points to process. For example if
--start-index is 1000 and --end-index is 2000, then only plates or points 1000 through
1999 are processed. This is useful for parallelizing large shape models on
multiple machines.
--save-plate-centers If specified, the centers of all plates in the shape model are saved to an
additional file.
--suffix <value> If specified, the suffix will be appended to all output files. This is needed when
splitting large shape models into mulitple runs so that each run will be output to
different files.
--output-folder <folder>
Path to folder in which to place output files (defualt is current directory).
(If you run it with no arguments, this is printed out).
So for example, to compute the gravity at all plate centers using the
Werner method for Itokawa, you would do:
./gravity -d 1.95 -r .000143857148947075 ver64q.tab
where -d 1.95 specifies the density, and -r .000143857148947075
specifies the rotation rate, and ver64q.tab contains the shape
model. There is no need to specify --werner or --centers since these
are the default values. On output you should see the files
ver64q.tab-potential.txt, ver64q.tab-acceleration.txt,
ver64q.tab-acceleration-magnitude.txt in the current directory which
contain the computed potential, acceleration and acceleration
magnitude.
To run using Andy Cheng's method instead, you would type:
./gravity -d 1.95 -r .000143857148947075 --cheng ver64q.tab
Note the addition of the --cheng option.
The program also prints out timing information.
In addition there's an option for computing the potential and
acceleration at arbitrary coordinates rather than at the vertices and
centers of the plates. To use this option, specify the --file options
followed by the path to a file containing a list of coordinates in
body fixed coordinates, with one coordinate per line. For example, the
following is a valid file:
-0.15153 0.08183 0.07523
-0.14726 0.08251 0.07657
-0.14288 0.08309 0.07759
-0.13851 0.08366 0.07864
-0.13436 0.08447 0.08038
By default the first 3 columns of the file are assumed to contain the
x, y, and z coordinates of the points. If the data is contained in
other columns, use the --columns option to specify which columns
should be read. For example if the data is contained in the 4th, 5th,
and 6th columns, you would add this option:
--columns 3,4,5
Note the column numbers are zero based (so 0 is the first columns).
Note also that if the --file option is provided and you also provide a
value for the reference potential with the --ref-potential option,
then the elevation is also computed for each point and is saved out in
a separate file (In the example above, this would be
ver64q.tab-elevation.txt). Note that elevation is only evaluated this
way when you have a list of arbitrary points using the --file
option. To compute elevation and slope at all plate center, use the
elevation-slope program, described below.
Note also to compute the reference potential for an arbitrary shape
model (which you would need to compute elevation at arbitrary points),
run the elevation-slope program described below and the reference
potential value will be printed out to standard output.
------------------------------------
References
------------------------------------
[1] Werner, R.A. and Scheeres, D.J., 1997, Exterior gravitation of a
polyhedron derived and compared with harmonic and mascon gravitation
representations of asteroid 4769 Castalia, Celestial Mechanics and
Dynamical Astronomy, Vol. 65, pp. 313-344.
[2] Cheng, A.F. et al., 2012, Efficient Calculation of Effective
Potential and Gravity on Small Bodies, ACM, 1667, p. 6447.

View File

@@ -0,0 +1,135 @@
#include <vector>
#include "platemodel.h"
using namespace std;
/*
These functions compute gravitation potential and acceleration
of a closed triangular plate model using the approximation derived by A. Cheng.
*/
struct FaceCenters
{
double center[3];
double normal[3]; // with length equal to twice plate area
};
static vector<FaceCenters> faceCenters;
static Platemodel* polyData = 0;
Platemodel* initializeGravityCheng(const char* filename)
{
if (polyData != 0)
delete polyData;
polyData = new Platemodel();
polyData->load(filename);
int pointIds[3];
// Compute the face data
int numFaces = polyData->getNumberOfPlates();
faceCenters.resize(numFaces);
for (int i=0; i<numFaces; ++i)
{
FaceCenters fc;
// Get center of cell
polyData->getPlatePoints(i, pointIds);
int p1 = pointIds[0];
int p2 = pointIds[1];
int p3 = pointIds[2];
double pt1[3];
double pt2[3];
double pt3[3];
polyData->getPoint(p1, pt1);
polyData->getPoint(p2, pt2);
polyData->getPoint(p3, pt3);
TriangleCenter(pt1, pt2, pt3, fc.center);
polyData->getNormal(i, fc.normal);
double area = TriangleArea(pt1, pt2, pt3);
MultiplyScalar(fc.normal, 2.0 * area);
faceCenters[i] = fc;
}
return polyData;
}
double getGravityCheng(const double fieldPoint[3], double acc[3])
{
double potential = 0.0;
acc[0] = 0.0;
acc[1] = 0.0;
acc[2] = 0.0;
int pointIds[3];
// Compute the edge data
int numFaces = polyData->getNumberOfPlates();
for (int i=0; i<numFaces; ++i)
{
const FaceCenters& fc = faceCenters[i];
double x_minus_R[3];
Subtract(fieldPoint, fc.center, x_minus_R);
double x_minus_R_dot_N = Dot(x_minus_R, fc.normal);
double mag_x_minus_R = Norm(x_minus_R);
if (mag_x_minus_R == 0.0)
{
// No contribution to potential if we reach here. Only acceleration.
polyData->getPlatePoints(i, pointIds);
int p1 = pointIds[0];
int p2 = pointIds[1];
int p3 = pointIds[2];
double pt1[3];
double pt2[3];
double pt3[3];
polyData->getPoint(p1, pt1);
polyData->getPoint(p2, pt2);
polyData->getPoint(p3, pt3);
double _2vjik[3] = {
2.0 * pt2[0] - pt1[0] - pt3[0],
2.0 * pt2[1] - pt1[1] - pt3[1],
2.0 * pt2[2] - pt1[2] - pt3[2]
};
double _2vijk[3] = {
2.0 * pt1[0] - pt2[0] - pt3[0],
2.0 * pt1[1] - pt2[1] - pt3[1],
2.0 * pt1[2] - pt2[2] - pt3[2]
};
double _2vkij[3] = {
2.0 * pt3[0] - pt1[0] - pt2[0],
2.0 * pt3[1] - pt1[1] - pt2[1],
2.0 * pt3[2] - pt1[2] - pt2[2]
};
double factor = 3.0 / Norm(_2vjik) + 3.0 / Norm(_2vijk) + 3.0 / Norm(_2vkij);
acc[0] -= fc.normal[0] * factor;
acc[1] -= fc.normal[1] * factor;
acc[2] -= fc.normal[2] * factor;
}
else
{
potential += x_minus_R_dot_N / mag_x_minus_R;
acc[0] -= ( (fc.normal[0] - x_minus_R[0] * x_minus_R_dot_N / (mag_x_minus_R*mag_x_minus_R)) / mag_x_minus_R );
acc[1] -= ( (fc.normal[1] - x_minus_R[1] * x_minus_R_dot_N / (mag_x_minus_R*mag_x_minus_R)) / mag_x_minus_R );
acc[2] -= ( (fc.normal[2] - x_minus_R[2] * x_minus_R_dot_N / (mag_x_minus_R*mag_x_minus_R)) / mag_x_minus_R );
}
}
potential *= 0.25;
acc[0] *= 0.25;
acc[1] *= 0.25;
acc[2] *= 0.25;
return potential;
}

View File

@@ -0,0 +1,10 @@
#ifndef GRAVITY_CHENG_H
#define GRAVITY_CHENG_H
#include "platemodel.h"
Platemodel* initializeGravityCheng(const char* filename);
double getGravityCheng(const double fieldPoint[3], double acc[3]);
#endif // GRAVITY_CHENG_H

View File

@@ -0,0 +1,22 @@
#ifndef GRAVITY_POINT_H
#define GRAVITY_POINT_H
#include "mathutil.h"
inline double getGravityPoint(const double fieldPoint[3], double acc[3])
{
double rhat[3] = {fieldPoint[0], fieldPoint[1], fieldPoint[2]};
double r = Normalize(rhat);
double r2 = fieldPoint[0]*fieldPoint[0] + fieldPoint[1]*fieldPoint[1] + fieldPoint[2]*fieldPoint[2];
double potential = 1.0 / r;
acc[0] = -rhat[0] / r2;
acc[1] = -rhat[1] / r2;
acc[2] = -rhat[2] / r2;
return potential;
}
#endif // GRAVITY_POINT_H

View File

@@ -0,0 +1,357 @@
#include <vector>
#include "mathutil.h"
#include "platemodel.h"
#include <unordered_map>
using namespace std;
struct EdgeKey
{
int p1;
int p2;
};
struct EdgeHash
{
size_t
operator()(const EdgeKey& key) const
{
// This hash seems to produce efficient look ups. Not sure what
// the best hash is though.
return key.p1;
}
};
static bool operator==(const EdgeKey& key1, const EdgeKey& key2)
{
return key1.p1 == key2.p1 && key1.p2 == key2.p2;
}
struct EdgeData
{
double E[3][3];
double edgeLength;
int p1;
int p2;
};
struct FaceData
{
double F[3][3];
int p1;
int p2;
int p3;
};
struct PointData
{
double r[3];
double r_mag;
};
typedef unordered_map<EdgeKey, EdgeData, EdgeHash> EdgeDataMap;
static vector<EdgeData> edgeData;
static vector<FaceData> faceData;
static Platemodel* polyData = 0;
static vector<PointData> pointData;
static void addMatrices(double a[3][3], double b[3][3], double c[3][3])
{
for (int i=0; i<3; ++i)
for (int j=0; j<3; ++j)
c[i][j] = a[i][j] + b[i][j];
}
static void Multiply3x3(const double A[3][3], const double v[3], double u[3])
{
u[0] = A[0][0]*v[0] + A[0][1]*v[1] + A[0][2]*v[2];
u[1] = A[1][0]*v[0] + A[1][1]*v[1] + A[1][2]*v[2];
u[2] = A[2][0]*v[0] + A[2][1]*v[1] + A[2][2]*v[2];
}
static double Abs(double a)
{
return (a <= 0.0) ? 0.0 - a : a;
}
/*
// For debugging
static void printmatrix(double m[3][3])
{
for (int i=0; i<3; ++i)
{
for (int j=0; j<3; ++j)
cout << m[i][j] << " ";
cout << endl;
}
}
static void printvec(const char* str, double m[3])
{
cout << str << " : ";
for (int j=0; j<3; ++j)
cout << m[j] << " ";
cout << endl;
}
*/
/*
These functions compute gravitation potential and acceleration
of a closed triangular plate model using the method of Werner as
described in Werner R. A. and D. J. Scheeres (1997) CeMDA, 65, 313-344.
*/
Platemodel* initializeGravityWerner(const char* filename)
{
if (polyData != 0)
delete polyData;
EdgeDataMap edgeDataMap;
polyData = new Platemodel();
polyData->load(filename);
int pointIds[3];
int numFaces = polyData->getNumberOfPlates();
// Compute the edge data
for (int i=0; i<numFaces; ++i)
{
polyData->getPlatePoints(i, pointIds);
double cellNormal[3];
polyData->getNormal(i, cellNormal);
for (int j=0; j<3; ++j)
{
int p1;
int p2;
if (j < 2)
{
p1 = pointIds[j];
p2 = pointIds[j+1];
}
else
{
p1 = pointIds[2];
p2 = pointIds[0];
}
// Put the point with the lowest id into ed so that
// the 2 identical edges always have the same point
EdgeKey key;
if (p1 < p2)
{
key.p1 = p1;
key.p2 = p2;
}
else
{
key.p1 = p2;
key.p2 = p1;
}
// If key not found
EdgeDataMap::iterator it = edgeDataMap.find(key);
if (it == edgeDataMap.end())
{
EdgeData ed;
ed.E[0][0] = ed.E[0][1] = ed.E[0][2] = 0.0;
ed.E[1][0] = ed.E[1][1] = ed.E[1][2] = 0.0;
ed.E[2][0] = ed.E[2][1] = ed.E[2][2] = 0.0;
ed.edgeLength = 0.0;
ed.p1 = key.p1;
ed.p2 = key.p2;
it = edgeDataMap.insert(pair<EdgeKey,EdgeData>(key,ed)).first;
}
EdgeData& ed = it->second;
// Compute unit vector from p1 to p2
double edgeUnitVector[3];
double pt1[3];
double pt2[3];
polyData->getPoint(p1, pt1);
polyData->getPoint(p2, pt2);
Subtract(pt2, pt1, edgeUnitVector);
ed.edgeLength = Normalize(edgeUnitVector);
// Compute half of the E dyad
double edgeNormal[3];
Cross(edgeUnitVector, cellNormal, edgeNormal);
double E[3][3];
Outer(cellNormal, edgeNormal, E);
addMatrices(ed.E, E, ed.E);
}
}
// Now convert the edgeDataMap to a vector
edgeData.resize(numFaces*3/2);
int i = 0;
EdgeDataMap::const_iterator it = edgeDataMap.begin();
EdgeDataMap::const_iterator end = edgeDataMap.end();
for (; it != end; ++it)
{
edgeData[i] = it->second;
++i;
}
// Compute the face data
faceData.resize(numFaces);
for (int i=0; i<numFaces; ++i)
{
FaceData fd;
polyData->getPlatePoints(i, pointIds);
fd.p1 = pointIds[0];
fd.p2 = pointIds[1];
fd.p3 = pointIds[2];
// Compute the F dyad
double normal[3];
polyData->getNormal(i, normal);
Outer(normal, normal, fd.F);
faceData[i] = fd;
}
return polyData;
}
static double compute_wf(const FaceData& fd)
{
const PointData& pd1 = pointData[fd.p1];
const PointData& pd2 = pointData[fd.p2];
const PointData& pd3 = pointData[fd.p3];
double cross[3];
Cross(pd2.r, pd3.r, cross);
double numerator = Dot(pd1.r, cross);
double denominator = pd1.r_mag*pd2.r_mag*pd3.r_mag +
pd1.r_mag*Dot(pd2.r,pd3.r) +
pd2.r_mag*Dot(pd3.r,pd1.r) +
pd3.r_mag*Dot(pd1.r,pd2.r);
if (Abs(numerator) < 1e-9)
numerator = -0.0;
return 2.0 * atan2(numerator, denominator);
}
static double compute_Le(const EdgeData& ed)
{
const PointData& pd1 = pointData[ed.p1];
const PointData& pd2 = pointData[ed.p2];
if ( Abs(pd1.r_mag + pd2.r_mag - ed.edgeLength) < 1e-9)
{
return 0.0;
}
return log ( (pd1.r_mag + pd2.r_mag + ed.edgeLength) / (pd1.r_mag + pd2.r_mag - ed.edgeLength) );
}
double getGravityWerner(const double fieldPoint[3], double acc[])
{
double potential = 0.0;
if (acc)
{
acc[0] = 0.0;
acc[1] = 0.0;
acc[2] = 0.0;
}
// Cache all the vectors from field point to vertices and their magnitudes
int numPoints = polyData->getNumberOfPoints();
pointData.resize(numPoints);
for (int i=0; i<numPoints; ++i)
{
PointData& pd = pointData[i];
polyData->getPoint(i, pd.r);
Subtract(pd.r, fieldPoint, pd.r);
pd.r_mag = Norm(pd.r);
}
double Er[3];
double rEr;
double Fr[3];
double rFr;
int numEdges = edgeData.size();
for (int i=0; i<numEdges; ++i)
{
const EdgeData& ed = edgeData[i];
// Any vertex of the cell will do, so just choose the first one.
const PointData& pd = pointData[ed.p1];
double Le = compute_Le(ed);
Multiply3x3(ed.E, pd.r, Er);
rEr = Dot(pd.r, Er);
potential -= (rEr * Le);
if (acc)
{
acc[0] -= Er[0]*Le;
acc[1] -= Er[1]*Le;
acc[2] -= Er[2]*Le;
}
}
int numFaces = polyData->getNumberOfPlates();
for (int i=0; i<numFaces; ++i)
{
const FaceData& fd = faceData[i];
// Any vertex of the cell will do, so just choose the first one.
const PointData& pd = pointData[fd.p1];
double wf = compute_wf(fd);
Multiply3x3(fd.F, pd.r, Fr);
rFr = Dot(pd.r, Fr);
potential += (rFr * wf);
if (acc)
{
acc[0] += Fr[0]*wf;
acc[1] += Fr[1]*wf;
acc[2] += Fr[2]*wf;
}
}
return 0.5 * potential;
}
bool isInsidePolyhedron(const double fieldPoint[3])
{
// Cache all the vectors from field point to vertices and their magnitudes
int numPoints = polyData->getNumberOfPoints();
pointData.resize(numPoints);
for (int i=0; i<numPoints; ++i)
{
PointData& pd = pointData[i];
polyData->getPoint(i, pd.r);
Subtract(pd.r, fieldPoint, pd.r);
pd.r_mag = Norm(pd.r);
}
double sum = 0.0;
int numFaces = polyData->getNumberOfPlates();
for (int i=0; i<numFaces; ++i)
{
const FaceData& fd = faceData[i];
sum += compute_wf(fd);
}
// This sum is equal to 4*pi if the point is inside the polyhedron and
// equals zero when outside.
return sum >= 2.0*M_PI;
}

View File

@@ -0,0 +1,12 @@
#ifndef GRAVITY_WERNER_H
#define GRAVITY_WERNER_H
#include "mathutil.h"
#include "platemodel.h"
Platemodel* initializeGravityWerner(const char* filename);
double getGravityWerner(const double fieldPoint[3], double acc[3]);
bool isInsidePolyhedron(const double fieldPoint[3]);
#endif // GRAVITY_H

View File

@@ -0,0 +1,561 @@
#include <iostream>
#include <fstream>
#include <time.h>
#include <libgen.h>
#include "SpiceUsr.h"
#include "gravity-werner.h"
#include "gravity-cheng.h"
#include "gravity-point.h"
#include "util.h"
#include <fenv.h>
#include <time.h>
#include <stdlib.h>
#include <string.h>
using namespace std;
//old value for gravitational constant
//static const double g_G = 6.67384e-11 * 1.0e-9;
//gravitational constant new value as of Aug 16, 2017. units in m^3/kgs^2
static const double defaultG = 6.67408e-11;
typedef enum GravityAlgorithmType
{
WERNER,
CHENG,
POINT_SOURCE
} GravityAlgorithmType;
typedef enum HowToEvaluateAtPlate
{
EVALUATE_AT_CENTER,
EVALUATE_AT_VERTEX,
AVERAGE_VERTICES,
FROM_FILE
} HowToEvaluateAtPlate;
struct GravityResult
{
double potential;
double acc[3];
double elevation;
bool filled;
};
static void saveResults(char* pltfile,
string outputFolder,
const vector<GravityResult>& results,
bool saveElevation,
string suffix)
{
string pltfilebasename = basename(pltfile);
string outputPot = outputFolder + "/" + pltfilebasename + "-potential.txt" + suffix;
string outputAcc = outputFolder + "/" + pltfilebasename + "-acceleration.txt" + suffix;
cout << "saving to potential file:" << outputPot << "\n";
cout << "saving to acceleration file:" << outputAcc << "\n";
ofstream foutP(outputPot.c_str());
if (!foutP.is_open())
{
cerr << "Error: Unable to open file for writing" << endl;
exit(1);
}
foutP.precision(16);
ofstream foutA(outputAcc.c_str());
if (!foutA.is_open())
{
cerr << "Error: Unable to open file for writing" << endl;
exit(1);
}
foutA.precision(16);
int size = results.size();
for (int i=0; i<size; ++i)
{
foutP << results[i].potential << endl;
foutA << results[i].acc[0] << " " << results[i].acc[1] << " " << results[i].acc[2] << endl;
}
foutP.close();
foutA.close();
if (saveElevation)
{
string outputElev = outputFolder + "/" + pltfilebasename + "-elevation.txt" + suffix;
ofstream foutE(outputElev.c_str());
if (!foutE.is_open())
{
cerr << "Error: Unable to open file for writing" << endl;
exit(1);
}
foutE.precision(16);
for (int i=0; i<size; ++i)
{
foutE << results[i].elevation << endl;
}
foutE.close();
}
}
static void usage()
{
cout << "This program computes the gravitational acceleration and potential of a \n"
<< "shape model at specified points and saves the values to files.\n\n"
<< "Usage: gravity [options] <platemodelfile>\n\n"
<< "Where:\n"
<< " <platemodelfile> Path to shape model file in OBJ or Gaskell PLT format.\n\n"
<< "Options:\n"
<< " -d <value> Density of shape model in g/cm^3 (default is 1)\n"
<< " -r <value> Rotation rate of shape model in radians/sec (default is 0)\n"
<< " --gravConst <value> Change the gravitational constant to use. Units are (in gcm^3/s^2)\n"
<< " (default is " << defaultG << ")\n"
<< " --werner Use the Werner algorithm for computing the gravity (this is the\n"
<< " default if neither --werner or --cheng option provided)\n"
<< " --cheng Use Andy Cheng's algorithm for computing the gravity (default is to\n"
<< " use Werner method if neither --werner or --cheng option provided)\n"
<< " --centers Evaluate gravity directly at the centers of plates (this is the default\n"
<< " if neither --centers or -vertices or --file option provided)\n"
<< " --average-vertices Evaluate gravity of each plate by averaging the gravity computed at the\n"
<< " 3 vertices of the plate (default is to evaluate at centers)\n"
<< " --vertices Evaluate gravity directly at each vertex (default is to evaluate at centers)\n"
<< " When using this option, you must also add the --cheng option since singularities\n"
<< " occur at the vertices with the Werner algorithm.\n"
<< " --file <filename> Evaluate gravity at points specified in file (default is to evaluate\n"
<< " at centers)\n"
<< " --ref-potential <value> If the --file option is provided, then use this option to specify the reference\n"
<< " potential (in J/kg) which is needed for calculating elevation. This option is ignored if\n"
<< " --file is not provided. If --file is provided but --ref-potential is not\n"
<< " provided then no elevation data is saved out.\n"
<< " --columns <int,int,int> If --file is provided, then this options controls which columns of the file are\n"
<< " assumed to contain the x, y, and z coordinates of the points. By default, columns\n"
<< " 0, 1, and 2 are read. If you wanted, say, columns 3, 4, and 5 instead you would\n"
<< " include this option as for example: --columns 3,4,5. Note that they are separated\n"
<< " by commas (no spaces) and are zero based. If --file is not provided, then this\n"
<< " option is ignored.\n"
<< " --start-index <value>\n"
<< " --end-index <value> use these 2 options to specify a range of plates or points to process. For example if\n"
<< " --start-index is 1000 and --end-index is 2000, then only plates or points 1000 through\n"
<< " 1999 are processed. This is useful for parallelizing large shape models on\n"
<< " multiple machines.\n"
<< " --suffix <value> If specified, the suffix will be appended to all output files. This is needed when\n"
<< " splitting large shape models into mulitple runs so that each run will be output to\n"
<< " different files.\n"
<< " --output-folder <folder>\n"
<< " Path to folder in which to place output files (default is current directory).\n"
<< endl;
exit(1);
}
int main(int argc, char** argv)
{
const int numberRequiredArgs = 1;
if (argc-1 < numberRequiredArgs)
usage();
double density = 1.0;
double omega = 0.0;
//double density = 2.67; // for Eros
//double density = 1.95; // for Itokawa
//double density = 3.456; // for Vesta
//double density = 2.5; // for Andy Cheng's gravity poster
//double omega = 0.0003311657616706400000;// for Eros (in radians per second)
//double omega = 0.000143857148947075; // for Itokawa (in radians per second)
//double omega = (1617.3329428 / 86400.0) * (M_PI / 180.0); // for Vesta (in radians per second)
GravityAlgorithmType gravityType = WERNER;
HowToEvaluateAtPlate howToEvalute = EVALUATE_AT_CENTER;
char* fieldpointsfile = 0;
double refPotential = 0.0;
bool refPotentialProvided = false;
int fileColumns[3] = {0, 1, 2};
int startIndex = -1;
int endIndex = -1;
string suffix = "";
string outputFolder = ".";
double g_G = defaultG;
int i = 1;
for(; i<argc; ++i)
{
if (!strcmp(argv[i], "-d"))
{
density = atof(argv[++i]);
}
else if (!strcmp(argv[i], "-r"))
{
omega = atof(argv[++i]);
}
else if (!strcmp(argv[i], "--werner"))
{
gravityType = WERNER;
}
else if (!strcmp(argv[i], "--cheng"))
{
gravityType = CHENG;
}
else if (!strcmp(argv[i], "--pointsource"))
{
gravityType = POINT_SOURCE;
}
else if (!strcmp(argv[i], "--centers"))
{
howToEvalute = EVALUATE_AT_CENTER;
}
else if (!strcmp(argv[i], "--average-vertices"))
{
howToEvalute = AVERAGE_VERTICES;
}
else if (!strcmp(argv[i], "--vertices"))
{
howToEvalute = EVALUATE_AT_VERTEX;
}
else if (!strcmp(argv[i], "--file"))
{
howToEvalute = FROM_FILE;
fieldpointsfile = argv[++i];
}
else if (!strcmp(argv[i], "--ref-potential"))
{
refPotential = atof(argv[++i]);
refPotentialProvided = true;
}
else if (!strcmp(argv[i], "--columns"))
{
vector<string> tokens = split(argv[++i], ',');
if (tokens.size() != 3)
usage();
fileColumns[0] = atoi(tokens[0].c_str());
fileColumns[1] = atoi(tokens[1].c_str());
fileColumns[2] = atoi(tokens[2].c_str());
}
else if (!strcmp(argv[i], "--start-index"))
{
startIndex = atoi(argv[++i]);
}
else if (!strcmp(argv[i], "--end-index"))
{
endIndex = atoi(argv[++i]);
}
else if (!strcmp(argv[i], "--suffix"))
{
suffix = argv[++i];
}
else if (!strcmp(argv[i], "--output-folder"))
{
outputFolder = argv[++i];
}
else if (!strcmp(argv[i], "--gravConst"))
{
g_G = atof(argv[++i]);
}
else
{
break;
}
}
//convert grav constant to units of meters and grams
g_G = g_G * 1.0e-9;
cout << "gravitational constant (in gcm^3/s^2) is " << g_G << "\n";
cout << "writing to outputFolder:" << outputFolder << "\n";
// There must be numRequiredArgs arguments remaining after the options. Otherwise abort.
if (argc - i != numberRequiredArgs)
usage();
if ((howToEvalute == EVALUATE_AT_VERTEX || howToEvalute == AVERAGE_VERTICES) && gravityType == WERNER)
{
cout << "Warning: When evaluating at vertices, use the Cheng algorithm since\n"
<< "singularities occur at vertices with the Werner algorithm. Continuing anyway."
<< endl;
}
char* pltfile = argv[i];
cout.setf(ios::fixed,ios::floatfield);
cout.precision(2);
clock_t t1, t2;
t1 = clock();
Platemodel* polyData = 0;
if (gravityType == WERNER)
polyData = initializeGravityWerner(pltfile);
else if (gravityType == CHENG)
polyData = initializeGravityCheng(pltfile);
else
abort();
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Initialization time: " << elapsed_time << " sec" << endl;
double acc[3] = {0.0, 0.0, 0.0};
double potential = 0.0;
vector<GravityResult> plateResults;
if (howToEvalute == FROM_FILE)
{
ifstream fin(fieldpointsfile);
if (fin.is_open())
{
t1 = clock();
string line;
int count = 0;
int lineNumber = -1;
while (getline(fin, line))
{
++lineNumber;
if (startIndex >= 0 && endIndex >= 0)
{
if (lineNumber < startIndex || lineNumber >= endIndex)
continue;
}
vector<string> tokens = split(line);
double pt[3] = {
atof(tokens[ fileColumns[0] ].c_str()),
atof(tokens[ fileColumns[1] ].c_str()),
atof(tokens[ fileColumns[2] ].c_str())
};
if (gravityType == WERNER)
potential = 1.0e6*1.0e12*g_G*density*getGravityWerner(pt, acc);
else
potential = 1.0e6*1.0e12*g_G*density*getGravityCheng(pt, acc);
acc[0] *= 1.0e3 * 1.0e12 * g_G * density;
acc[1] *= 1.0e3 * 1.0e12 * g_G * density;
acc[2] *= 1.0e3 * 1.0e12 * g_G * density;
// add centrifugal force
if (omega != 0.0)
{
potential -= 1.0e6 * 0.5 * omega*omega * (pt[0]*pt[0] + pt[1]*pt[1]);
acc[0] += 1.0e3 * omega*omega * pt[0];
acc[1] += 1.0e3 * omega*omega * pt[1];
// do nothing for z component
}
GravityResult result;
result.potential = potential;
result.acc[0] = acc[0];
result.acc[1] = acc[1];
result.acc[2] = acc[2];
if (refPotentialProvided)
result.elevation = (potential - refPotential) / Norm(acc);
plateResults.push_back(result);
if ((count+1) % 10000 == 0)
{
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Time to evaluate at " << count+1 << " points: " << elapsed_time << " sec" << endl;
}
++count;
}
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Time to evaluate total of " << count << " points: " << elapsed_time << " sec" << endl;
}
else
{
cerr << "Error: Unable to open file '" << fieldpointsfile << "'" << endl;
exit(1);
}
}
else
{
t1 = clock();
vector<GravityResult> pointResults;
int numFilled = 0;
if (howToEvalute == AVERAGE_VERTICES)
{
int numPoints = polyData->getNumberOfPoints();
pointResults.resize(numPoints);
for (i=0; i<numPoints; ++i)
pointResults[i].filled = false;
}
int idList[3];
int numPlates = polyData->getNumberOfPlates();
int numPoints = polyData->getNumberOfPoints();
if (startIndex < 0 || endIndex < 0)
{
startIndex = 0;
if (howToEvalute == EVALUATE_AT_VERTEX)
endIndex = numPoints;
else
endIndex = numPlates;
}
cout << "total number of indicies to process: " << (endIndex - startIndex) << endl;
int counter = 1;
for (i=startIndex; i<endIndex; ++i)
{
if (howToEvalute == EVALUATE_AT_CENTER)
{
polyData->getPlatePoints(i, idList);
double pt1[3];
double pt2[3];
double pt3[3];
polyData->getPoint(idList[0], pt1);
polyData->getPoint(idList[1], pt2);
polyData->getPoint(idList[2], pt3);
double center[3];
TriangleCenter(pt1, pt2, pt3, center);
if (gravityType == WERNER)
potential = 1.0e6*1.0e12*g_G*density*getGravityWerner(center, acc);
else
potential = 1.0e6*1.0e12*g_G*density*getGravityCheng(center, acc);
acc[0] *= 1.0e3 * 1.0e12 * g_G * density;
acc[1] *= 1.0e3 * 1.0e12 * g_G * density;
acc[2] *= 1.0e3 * 1.0e12 * g_G * density;
// add centrifugal force
if (omega != 0.0)
{
potential -= 1.0e6 * 0.5 * omega*omega * (center[0]*center[0] + center[1]*center[1]);
acc[0] += 1.0e3 * omega*omega * center[0];
acc[1] += 1.0e3 * omega*omega * center[1];
// do nothing for z component
}
if (counter % 10000 == 0)
{
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Time to evaluate " << counter << " plates: " << elapsed_time << " sec" << endl;
}
}
else if(howToEvalute == AVERAGE_VERTICES)
{
polyData->getPlatePoints(i, idList);
double pt[3];
acc[0] = 0.0;
acc[1] = 0.0;
acc[2] = 0.0;
potential = 0.0;
for (int j=0; j<3; ++j)
{
int ptId = idList[j];
GravityResult& result = pointResults[ptId];
if (!result.filled)
{
polyData->getPoint(ptId, pt);
if (gravityType == WERNER)
result.potential = 1.0e6*1.0e12*g_G*density*getGravityWerner(pt, result.acc);
else
result.potential = 1.0e6*1.0e12*g_G*density*getGravityCheng(pt, result.acc);
result.acc[0] *= 1.0e3 * 1.0e12 * g_G * density;
result.acc[1] *= 1.0e3 * 1.0e12 * g_G * density;
result.acc[2] *= 1.0e3 * 1.0e12 * g_G * density;
// add centrifugal force
if (omega != 0.0)
{
result.potential -= 1.0e6 * 0.5 * omega*omega * (pt[0]*pt[0] + pt[1]*pt[1]);
result.acc[0] += 1.0e3 * omega*omega * pt[0];
result.acc[1] += 1.0e3 * omega*omega * pt[1];
// do nothing for z component
}
result.filled = true;
if ((numFilled+1) % 10000 == 0)
{
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Time to evaluate " << numFilled+1 << " vertices: " << elapsed_time << " sec" << endl;
}
++numFilled;
}
potential += result.potential;
acc[0] += result.acc[0];
acc[1] += result.acc[1];
acc[2] += result.acc[2];
}
potential /= 3.0;
acc[0] /= 3.0;
acc[1] /= 3.0;
acc[2] /= 3.0;
}
else if (howToEvalute == EVALUATE_AT_VERTEX)
{
double pt[3];
polyData->getPoint(i, pt);
if (gravityType == WERNER)
potential = 1.0e6*1.0e12*g_G*density*getGravityWerner(pt, acc);
else
potential = 1.0e6*1.0e12*g_G*density*getGravityCheng(pt, acc);
acc[0] *= 1.0e3 * 1.0e12 * g_G * density;
acc[1] *= 1.0e3 * 1.0e12 * g_G * density;
acc[2] *= 1.0e3 * 1.0e12 * g_G * density;
// add centrifugal force
if (omega != 0.0)
{
potential -= 1.0e6 * 0.5 * omega*omega * (pt[0]*pt[0] + pt[1]*pt[1]);
acc[0] += 1.0e3 * omega*omega * pt[0];
acc[1] += 1.0e3 * omega*omega * pt[1];
// do nothing for z component
}
if (counter % 10000 == 0)
{
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Time to evaluate " << counter << " vertices: " << elapsed_time << " sec" << endl;
}
}
GravityResult result;
result.potential = potential;
result.acc[0] = acc[0];
result.acc[1] = acc[1];
result.acc[2] = acc[2];
plateResults.push_back(result);
counter++;
}
t2 = clock();
double elapsed_time = (double)(t2 - t1) / CLOCKS_PER_SEC;
cout << "Total loops: " << counter << endl;
cout << "Total time: " << elapsed_time << " sec" << endl;
}
// Only save out elevation if user provided a list of points and
// also specified a reference potential. If calculating at plate
// centers only, then a separate program must be used to calculate
// elevation and slope.
bool saveElevation = refPotentialProvided && howToEvalute == FROM_FILE;
saveResults(pltfile, outputFolder, plateResults, saveElevation, suffix);
return 0;
}

View File

@@ -0,0 +1,213 @@
#include "platemodel.h"
#include "util.h"
#include <fstream>
#include <string>
#include <vector>
#include <iostream>
#include <stdlib.h>
using namespace std;
Platemodel::Platemodel()
{
}
void Platemodel::loadGaskell(std::string filename)
{
ifstream fin(filename.c_str());
if (fin.is_open())
{
int numPoints = -1;
int numPlates = -1;
string line;
getline(fin, line);
trim(line);
vector<string> tokens = split(line);
if (tokens.size() >= 1)
numPoints = atoi(tokens[0].c_str());
if (tokens.size() == 2)
numPlates = atoi(tokens[1].c_str());
if (tokens.size() < 1 || tokens.size() > 2)
{
cerr << "Error: File format incorrect " << endl;
exit(1);
}
Vertex x;
for (int i=0; i<numPoints; ++i)
{
getline(fin, line);
trim(line);
tokens = split(line);
if (tokens.size() != 4)
{
cerr << "Error: File format incorrect" << endl;
exit(1);
}
x.point[0] = atof(tokens[1].c_str());
x.point[1] = atof(tokens[2].c_str());
x.point[2] = atof(tokens[3].c_str());
vertices.push_back(x);
}
if (numPlates < 0)
{
getline(fin, line);
trim(line);
vector<string> tokens = split(line);
if (tokens.size() != 1)
{
cerr << "Error: File format incorrect" << endl;
exit(1);
}
numPlates = atoi(tokens[0].c_str());
}
Plate p;
for (int i=0; i<numPlates; ++i)
{
getline(fin, line);
trim(line);
tokens = split(line);
if (tokens.size() != 4)
{
cerr << "Error: File format incorrect" << endl;
exit(1);
}
p.cell[0] = atoi(tokens[1].c_str())-1;
p.cell[1] = atoi(tokens[2].c_str())-1;
p.cell[2] = atoi(tokens[3].c_str())-1;
ComputeNormal(
vertices[p.cell[0]].point,
vertices[p.cell[1]].point,
vertices[p.cell[2]].point,
p.normal
);
plates.push_back(p);
}
}
else
{
cerr << "Error: Unable to open file '" << filename << "'" << endl;
exit(1);
}
}
void Platemodel::loadOBJ(std::string filename)
{
ifstream fin(filename.c_str());
if (fin.is_open())
{
Vertex x;
Plate p;
string line;
string type;
int lindex = 0;
while (getline(fin, line))
{
trim(line);
if (line.length() == 0 || line[0] == '#')
continue;
vector<string> tokens = split(line);
if (tokens.size() != 4)
{
cerr << "Error: File format incorrect" << endl;
cerr << "line content:" << line << endl;
exit(1);
}
type = tokens[0];
if (type == "v" || type == "V")
{
x.point[0] = atof(tokens[1].c_str());
x.point[1] = atof(tokens[2].c_str());
x.point[2] = atof(tokens[3].c_str());
vertices.push_back(x);
}
else if (type == "f" || type == "F")
{
p.cell[0] = atoi(tokens[1].c_str())-1;
p.cell[1] = atoi(tokens[2].c_str())-1;
p.cell[2] = atoi(tokens[3].c_str())-1;
ComputeNormal(
vertices[p.cell[0]].point,
vertices[p.cell[1]].point,
vertices[p.cell[2]].point,
p.normal
);
plates.push_back(p);
}
else
{
cerr << "Warning: Only lines beginning with 'v' or 'f' are currently parsed" << endl;
}
lindex = lindex + 1;
}
}
else
{
cerr << "Error: Unable to open file '" << filename << "'" << endl;
exit(1);
}
}
void Platemodel::load(std::string filename)
{
bool isObj = false;
// Try to guess the format. Assume OBJ if the first non blank or comment line
// begins with a v or f
{
ifstream fin(filename.c_str());
if (fin.is_open())
{
string line;
string type;
while (getline(fin, line))
{
trim(line);
if (line.length() == 0 || line[0] == '#')
continue;
vector<string> tokens = split(line);
if (tokens.size() == 0)
{
cerr << "Error: File format not recognized" << endl;
exit(1);
}
if (tokens.size() == 1 || tokens.size() == 2)
{
// If true, this is the first line of the a gaskell formatted file
// which contains either the number of vertices
// or both the number of vertices and the number of plates.
isObj = false;
break;
}
type = tokens[0];
if (type == "v" || type == "V" || type == "f" || type == "F")
{
// If true, this is an OBJ file
isObj = true;
break;
}
}
fin.close();
}
}
if (isObj)
loadOBJ(filename);
else
loadGaskell(filename);
}

View File

@@ -0,0 +1,94 @@
#ifndef PLATEMODEL_H
#define PLATEMODEL_H
#include <vector>
#include <string>
#include "mathutil.h"
/**
This is bare bones plate model class similar to VTK's vtkPolydata class
for use in programs which do not have a dependency on VTK.
*/
class Platemodel
{
struct Vertex
{
double point[3];
};
struct Plate
{
int cell[3];
double normal[3];
};
public:
Platemodel();
void load(std::string filename);
void getPoint(int i, double x[3])
{
const Vertex& pt = vertices[i];
x[0] = pt.point[0];
x[1] = pt.point[1];
x[2] = pt.point[2];
}
void getNormal(int i, double x[3])
{
const Plate& p = plates[i];
x[0] = p.normal[0];
x[1] = p.normal[1];
x[2] = p.normal[2];
}
void getPlatePoints(int i, int ids[3])
{
const Plate& p = plates[i];
ids[0] = p.cell[0];
ids[1] = p.cell[1];
ids[2] = p.cell[2];
}
void getPlateCenter(int i, double center[3])
{
const Plate& p = plates[i];
TriangleCenter(
vertices[p.cell[0]].point,
vertices[p.cell[1]].point,
vertices[p.cell[2]].point,
center
);
}
double getPlateArea(int i)
{
const Plate& p = plates[i];
return TriangleArea(
vertices[p.cell[0]].point,
vertices[p.cell[1]].point,
vertices[p.cell[2]].point
);
}
int getNumberOfPoints()
{
return vertices.size();
}
int getNumberOfPlates()
{
return plates.size();
}
private:
void loadGaskell(std::string filename);
void loadOBJ(std::string filename);
std::vector<Vertex> vertices;
std::vector<Plate> plates;
};
#endif // PLATEMODEL_H

View File

@@ -0,0 +1,95 @@
#!/bin/bash
# This script builds the gravity and lidar-optimize tools from the
# ALTWG distribution.
ARCH=$(uname -s)_$(uname -m)
if [ -z $1 ]; then
echo "Usage: $0 install_dir"
echo "e.g. $0 3rd-party/${ARCH}/altwg"
exit 0
fi
install_dir=$1
# Assume VTK and SPICE are installed
base=$(
cd $(dirname $install_dir)
pwd -P
)
export VTK_HOME=${base}/vtk
if [ -d ${VTK_HOME} ]; then
echo "VTK found in ${VTK_HOME}"
else
echo "VTK should be installed in ${VTK_HOME}. Please run install-vtk.sh before this script."
exit 0
fi
export SPICE_HOME=${base}/spice/JNISpice
if [ -d ${SPICE_HOME} ]; then
echo "SPICE found in ${SPICE_HOME}"
else
echo "SPICE should be installed in ${SPICE_HOME}. Run the install-spice.sh script first."
exit 0
fi
GSL_VERSION=2.8
export GSL_HOME=${base}/gsl
if [ ! -d $GSL_HOME ]; then
echo "Installing GSL"
if [ ! -e gsl-${GSL_VERSION}.tar.gz ]; then
curl -RO https://ftp.gnu.org/gnu/gsl/gsl-${GSL_VERSION}.tar.gz
fi
tar xfz gsl-${GSL_VERSION}.tar.gz
(
cd gsl-${GSL_VERSION}
./configure --prefix ${GSL_HOME}
# make install cannot find certain header files?
make -j4
make install
)
fi
mkdir -p $install_dir
if [ $? -ne 0 ]; then
echo "cannot create directory $install_dir"
exit 1
fi
if [ ! -w $install_dir ]; then
echo "cannot write to directory $install_dir"
exit 1
fi
pushd $install_dir >/dev/null
install_dir=$(pwd -P)
popd >/dev/null
echo "will install ALTWG tools to $install_dir"
set -e
# location of this script
DIR=$(
cd $(dirname $0)
pwd -P
)
BUILD_DIR=buildaltwg
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR
cd $BUILD_DIR
export VTK_DIR=${VTK_HOME}
cmake -DCMAKE_BUILD_TYPE:String=Release "-DCMAKE_CXX_FLAGS_RELEASE:String=-O2 -DNDEBUG" ${DIR}
cmake --build . --config Release
for file in gravity lidar-optimize; do
if [ "$(uname -s)" == "Darwin" ]; then
# set @rpath for macOS executables
install_name_tool -add_rpath @executable_path/../vtk/lib $file
fi
cp -p $file $install_dir
done
echo "installed ALTWG tools. You may remove $(pwd)."

View File

@@ -0,0 +1,109 @@
#include <stdio.h>
#include <vtkPolyDataReader.h>
#include <vtkPolyDataWriter.h>
#include <vtkOBJReader.h>
#include <vtkPolyData.h>
#include <vtkCellLocator.h>
#include <vtkPointLocator.h>
#include <vtkGenericCell.h>
#include <vtkMath.h>
#include "lidardata.h"
#include "util.h"
static bool usePointLocator;
static vtkPointLocator* pointLocator = 0;
static vtkCellLocator* cellLocator = 0;
static vtkGenericCell* genericCell = 0;
static vtkPolyData* polyData = 0;
void initializeVtk(const char* dskfile)
{
polyData = vtkPolyData::New();
if (endsWith(dskfile, ".vtk"))
{
vtkPolyDataReader* smallBodyReader = vtkPolyDataReader::New();
smallBodyReader->SetFileName(dskfile);
smallBodyReader->Update();
polyData->ShallowCopy(smallBodyReader->GetOutput());
usePointLocator = true;
}
else
{
vtkOBJReader* smallBodyReader = vtkOBJReader::New();
smallBodyReader->SetFileName(dskfile);
smallBodyReader->Update();
polyData->ShallowCopy(smallBodyReader->GetOutput());
usePointLocator = false;
}
if (usePointLocator)
{
// Initialize the point locator
pointLocator = vtkPointLocator::New();
pointLocator->FreeSearchStructure();
pointLocator->SetDataSet(polyData);
pointLocator->AutomaticOn();
pointLocator->SetMaxLevel(100);
pointLocator->SetNumberOfPointsPerBucket(1);
pointLocator->BuildLocator();
}
else
{
// Initialize the cell locator
cellLocator = vtkCellLocator::New();
cellLocator->FreeSearchStructure();
cellLocator->SetDataSet(polyData);
cellLocator->CacheCellBoundsOn();
cellLocator->AutomaticOn();
cellLocator->SetMaxLevel(100);
cellLocator->SetNumberOfCellsPerNode(1);
cellLocator->BuildLocator();
genericCell = vtkGenericCell::New();
}
}
void savePointCloudToVTK(const LidarPointCloud& pointCloud, const std::string& filename)
{
vtkPoints* points = vtkPoints::New();
for (unsigned int i=0; i<pointCloud.size(); ++i)
{
const Point& p = pointCloud[i];
points->InsertNextPoint(p.targetpos);
}
vtkPolyData* polyData = vtkPolyData::New();
polyData->SetPoints(points);
vtkPolyDataWriter* writer = vtkPolyDataWriter::New();
writer->SetInputData(polyData);
writer->SetFileName(filename.c_str());
writer->SetFileTypeToBinary();
writer->Update();
}
void findClosestPointVtk(const double* origin, double* closestPoint, int* found)
{
double point[3] = {origin[0], origin[1], origin[2]};
vtkIdType cellId;
if (usePointLocator)
{
vtkIdType vtkId = pointLocator->FindClosestPoint(point);
polyData->GetPoint(vtkId, closestPoint);
cellId = 1; // there will always be a closest point
}
else
{
int subId;
double dist2;
cellLocator->FindClosestPoint(point, closestPoint, genericCell, cellId, subId, dist2);
}
if (cellId >= 0)
*found = 1;
else
*found = 0;
}

View File

@@ -0,0 +1,10 @@
#ifndef __CLOSEST_POINT_VTK_H__
#define __CLOSEST_POINT_VTK_H__
#include "lidardata.h"
void initializeVtk(const char* dskfile);
void findClosestPointVtk(const double* origin, double* closestPoint, int* found);
void savePointCloudToVTK(const LidarPointCloud& pointCloud, const std::string& filename);
#endif

View File

@@ -0,0 +1,67 @@
# This script generates C code for the partial derivatives of the
# objective function used by the lidar point optimizer. The objective
# function is the sum of squared differences between the target points
# and the transformed source points. This script only computes the
# partial derivatives for one term of the sum. After running this
# script, the generated C code should be copied into the loop that
# computes the sum of squared difference between the points. See
# the file icp-gsl.cpp in the function 'grad' to see how this was
# done.
p0 = q0;
p1 = q1;
p2 = q2;
p3 = q3;
inv = 1.0 / sqrt(p0 * p0 + p1 * p1 + p2 * p2 + p3 * p3);
p0 = p0 * inv;
p1 = p1 * inv;
p2 = p2 * inv;
p3 = p3 * inv;
s0 = s0 - cor0;
s1 = s1 - cor1;
s2 = s2 - cor2;
a = s0;
b = s1;
c = s2;
w = p1 * a + p2 * b + p3 * c;
s0 = 2.0 * (p0 * (a * p0 - (p2 * c - p3 * b)) + w * p1) - a;
s1 = 2.0 * (p0 * (b * p0 - (p3 * a - p1 * c)) + w * p2) - b;
s2 = 2.0 * (p0 * (c * p0 - (p1 * b - p2 * a)) + w * p3) - c;
s0 = s0 + cor0;
s1 = s1 + cor1;
s2 = s2 + cor2;
s0 = s0 + x * m;
s1 = s1 + y * m;
s2 = s2 + z * m;
d2 = ( s0 - t0 )^2 + ( s1 - t1 )^2 + ( s2 - t2 )^2;
0;
0;
print_csrc(diff(d2,x));
0;
0;
print_csrc(diff(d2,y));
0;
0;
print_csrc(diff(d2,z));
0;
0;
print_csrc(diff(d2,q0));
0;
0;
print_csrc(diff(d2,q1));
0;
0;
print_csrc(diff(d2,q2));
0;
0;
print_csrc(diff(d2,q3));
0;
0;

View File

@@ -0,0 +1,326 @@
#include <stdio.h>
#include <vector>
#include "optimize-gsl.h"
#include "closest-point-vtk.h"
#include "lidardata.h"
#include "mathutil.h"
static LidarPointCloud g_source;
static LidarPointCloud g_target;
static double g_sourceCentroid[3];
static bool g_translationOnly;
static bool g_rotationOnly;
// The purpose of the following factor is as follows. When solving the optimization
// we want the values of the independent variables to be approximately the same size.
// Now the 4 elements of the rotation quaternion are usually less then 1. However, if
// the camera is far away from the asteroid, then the values of the spacecraft position
// may be orders of magnitude greater than the quaternion values. This difference can
// mess up the optimization. Therefore, before starting the optmization, we divide the
// position by g_MULT_FACTOR so as to make the values on the order of 1. g_MULT_FACTOR is
// set to the norma of the position. Then before each time we make use of the position,
// we multiply it by g_MULT_FACTOR to get back the correct value. This way the optimization
// sees all the independent variables as having approximately the same size.
static double g_MULT_FACTOR = 1.0;
/* Return distance squared between points x and y */
static double vdist2(const double x[3], const double y[3])
{
return ( ( x[0] - y[0] ) * ( x[0] - y[0] )
+ ( x[1] - y[1] ) * ( x[1] - y[1] )
+ ( x[2] - y[2] ) * ( x[2] - y[2] ) );
}
/* Add vectors v1 and v2 and store result in vout */
static void vadd(const double v1[3], const double v2[3], double vout[3])
{
vout[0] = v1[0] + v2[0];
vout[1] = v1[1] + v2[1];
vout[2] = v1[2] + v2[2];
}
static void centroid(const LidarPointCloud& points, double* centroid)
{
centroid[0] = 0.0;
centroid[1] = 0.0;
centroid[2] = 0.0;
int n = points.size();
int i;
for (i=0; i<n; ++i)
{
vadd(centroid, points[i].targetpos, centroid);
}
centroid[0] /= n;
centroid[1] /= n;
centroid[2] /= n;
}
static void findAllClosestPoints(const double* transformation)
{
const double* translation = transformation;
const double* rotation = &transformation[3];
g_target.clear();
int n = g_source.size();
int found;
for (int i=0; i<n; ++i)
{
Point s = g_source[i];
transformPoint(s.targetpos, g_sourceCentroid, translation, rotation, s.targetpos);
Point closestPoint;
findClosestPointVtk(s.targetpos, closestPoint.targetpos, &found);
g_target.push_back(closestPoint);
}
}
static double func(const double* transformation, void*)
{
const double translation[3] = {
g_MULT_FACTOR * transformation[0],
g_MULT_FACTOR * transformation[1],
g_MULT_FACTOR * transformation[2] };
double rotation[4];
normalizeQuaternion(&transformation[3], rotation);
int n = g_source.size();
double ssd = 0.0;
for (int i=0; i<n; ++i)
{
Point s = g_source[i];
Point t = g_target[i];
transformPoint(s.targetpos, g_sourceCentroid, translation, rotation, s.targetpos);
double dist2 = vdist2(s.targetpos, t.targetpos);
ssd += dist2;
}
return ssd;
}
static void grad(const double* transformation, double* df, void*)
{
const double* translation = transformation;
const double* rotation = &transformation[3];
double x = translation[0];
double y = translation[1];
double z = translation[2];
double q0 = rotation[0];
double q1 = rotation[1];
double q2 = rotation[2];
double q3 = rotation[3];
double cor0 = g_sourceCentroid[0];
double cor1 = g_sourceCentroid[1];
double cor2 = g_sourceCentroid[2];
double m = g_MULT_FACTOR;
double q12 = pow( (q1*q1)+(q2*q2)+(q3*q3)+(q0*q0),-(1.0/2.0));
double q32 = pow( (q1*q1)+(q2*q2)+(q3*q3)+(q0*q0),-(3.0/2.0));
for (int i=0; i<7; ++i)
df[0] = 0.0;
int n = g_source.size();
for (int i=0; i<n; ++i)
{
const Point& s = g_source[i];
const Point& t = g_target[i];
double s0 = s.targetpos[0];
double s1 = s.targetpos[1];
double s2 = s.targetpos[2];
double t0 = t.targetpos[0];
double t1 = t.targetpos[1];
double t2 = t.targetpos[2];
// The following crazy formulas for gradient components have been autogenerated by computer algebra system called GiNaC (http://www.ginac.de/).
// See the file gradient.ginsh for the ginsh script used to generate these formulas.
if (g_rotationOnly == false)
{
// df/dx
df[0] += -2.0*m*( s0+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q12+-2.0*cor0-x*m+t0+-2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1);
// df/dy
df[1] += -2.0*( t1+s1+-2.0*q0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+-2.0*q12*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)-m*y+-2.0*cor1)*m;
// df/dz
df[2] += -2.0*( s2+-2.0*q3*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+t2+-2.0*cor2-z*m)*m;
}
if (g_translationOnly == false)
{
// df/dq0
df[3] += 2.0*( 2.0*q3*q12*( q0*q32*( s1-cor1)*q2+q3*q0*q32*( s2-cor2)+q0*q32*( s0-cor0)*q1)+2.0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+2.0*q3*q0*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+-2.0*( q12*( s2-cor2)-q0*q32*q2*( s0-cor0)+q0*q32*( s1-cor1)*q1-(q0*q0)*q32*( s2-cor2))*q0*q12+-2.0*(q0*q0)*q32*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0)))*( s2+-2.0*q3*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+t2+-2.0*cor2-z*m)+2.0*( t1+s1+-2.0*q0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+-2.0*q12*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)-m*y+-2.0*cor1)*( 2.0*q0*q32*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*(q0*q0)*q32*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+2.0*q0*q12*( (q0*q0)*q32*( s1-cor1)-q3*q0*q32*( s0-cor0)-q12*( s1-cor1)+q0*q32*( s2-cor2)*q1)+2.0*q12*q2*( q0*q32*( s1-cor1)*q2+q3*q0*q32*( s2-cor2)+q0*q32*( s0-cor0)*q1)+-2.0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1)))+2.0*( 2.0*q0*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1+2.0*( q3*q0*q32*( s1-cor1)-q12*( s0-cor0)-q0*q32*q2*( s2-cor2)+(q0*q0)*q32*( s0-cor0))*q0*q12+2.0*q12*( q0*q32*( s1-cor1)*q2+q3*q0*q32*( s2-cor2)+q0*q32*( s0-cor0)*q1)*q1+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q12+2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*(q0*q0)*q32)*( s0+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q12+-2.0*cor0-x*m+t0+-2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1);
// df/dq1
df[4] += 2.0*( -2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*(q1*q1)+2.0*( q3*q32*( s1-cor1)*q1-q32*q2*( s2-cor2)*q1+q0*q32*( s0-cor0)*q1)*q0*q12+2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q32*q1+2.0*q12*( q3*q32*( s2-cor2)*q1-q12*( s0-cor0)+q32*( s1-cor1)*q2*q1+q32*( s0-cor0)*(q1*q1))*q1)*( s0+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q12+-2.0*cor0-x*m+t0+-2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1)+2.0*( 2.0*( q32*q2*( s0-cor0)*q1-q32*( s1-cor1)*(q1*q1)+q12*( s1-cor1)+q0*q32*( s2-cor2)*q1)*q0*q12+-2.0*q0*q32*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))*q1+2.0*q3*q12*( q3*q32*( s2-cor2)*q1-q12*( s0-cor0)+q32*( s1-cor1)*q2*q1+q32*( s0-cor0)*(q1*q1))+2.0*q3*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1)*( s2+-2.0*q3*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+t2+-2.0*cor2-z*m)+2.0*( 2.0*q0*q32*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))*q1+2.0*q12*q2*( q3*q32*( s2-cor2)*q1-q12*( s0-cor0)+q32*( s1-cor1)*q2*q1+q32*( s0-cor0)*(q1*q1))+2.0*q32*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1+-2.0*( q3*q32*( s0-cor0)*q1+q12*( s2-cor2)-q32*( s2-cor2)*(q1*q1)-q0*q32*( s1-cor1)*q1)*q0*q12)*( t1+s1+-2.0*q0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+-2.0*q12*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)-m*y+-2.0*cor1);
// df/dq2
df[5] += 2.0*( 2.0*q0*q12*( q12*( s2-cor2)+q3*q32*( s1-cor1)*q2+q0*q32*q2*( s0-cor0)-q32*(q2*q2)*( s2-cor2))+2.0*q32*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1+2.0*q12*( q32*q2*( s0-cor0)*q1+q3*q32*q2*( s2-cor2)-q12*( s1-cor1)+q32*( s1-cor1)*(q2*q2))*q1+2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q32*q2)*( s0+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q12+-2.0*cor0-x*m+t0+-2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1)+2.0*( s2+-2.0*q3*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+t2+-2.0*cor2-z*m)*( -2.0*q0*q12*( q12*( s0-cor0)-q0*q32*q2*( s2-cor2)-q32*(q2*q2)*( s0-cor0)+q32*( s1-cor1)*q2*q1)+2.0*q3*q12*( q32*q2*( s0-cor0)*q1+q3*q32*q2*( s2-cor2)-q12*( s1-cor1)+q32*( s1-cor1)*(q2*q2))+-2.0*q0*q32*q2*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+2.0*q3*q32*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2))+2.0*( -2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q32*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))*q2+2.0*q0*q12*( q32*q2*( s2-cor2)*q1-q3*q32*q2*( s0-cor0)+q0*q32*( s1-cor1)*q2)+2.0*q32*(q2*q2)*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q12*q2*( q32*q2*( s0-cor0)*q1+q3*q32*q2*( s2-cor2)-q12*( s1-cor1)+q32*( s1-cor1)*(q2*q2)))*( t1+s1+-2.0*q0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+-2.0*q12*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)-m*y+-2.0*cor1);
// df/dq3
df[6] += 2.0*( 2.0*q3*q0*q32*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+2.0*q12*( q3*q32*( s0-cor0)*q1-q12*( s2-cor2)+q3*q32*( s1-cor1)*q2+(q3*q3)*q32*( s2-cor2))*q2+2.0*( q3*q32*( s2-cor2)*q1+q3*q0*q32*( s1-cor1)+q12*( s0-cor0)-(q3*q3)*q32*( s0-cor0))*q0*q12+2.0*q3*q32*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2))*( t1+s1+-2.0*q0*q12*( q12*( s2-cor2)*q1-q3*q12*( s0-cor0)+q0*q12*( s1-cor1))+-2.0*q12*q2*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)-m*y+-2.0*cor1)+2.0*( 2.0*q0*q12*( (q3*q3)*q32*( s1-cor1)-q3*q32*q2*( s2-cor2)+q3*q0*q32*( s0-cor0)-q12*( s1-cor1))+2.0*q12*( q3*q32*( s0-cor0)*q1-q12*( s2-cor2)+q3*q32*( s1-cor1)*q2+(q3*q3)*q32*( s2-cor2))*q1+2.0*q3*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1+2.0*q3*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q32)*( s0+-2.0*( q0*q12*( s0-cor0)-q12*q2*( s2-cor2)+q3*q12*( s1-cor1))*q0*q12+-2.0*cor0-x*m+t0+-2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)*q1)+2.0*( s2+-2.0*q3*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+2.0*q0*q12*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+t2+-2.0*cor2-z*m)*( -2.0*q12*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2)+-2.0*q3*q0*q32*( q12*( s1-cor1)*q1-q0*q12*( s2-cor2)-q12*q2*( s0-cor0))+2.0*q3*q12*( q3*q32*( s0-cor0)*q1-q12*( s2-cor2)+q3*q32*( s1-cor1)*q2+(q3*q3)*q32*( s2-cor2))+-2.0*q0*q12*( q3*q32*( s1-cor1)*q1-q3*q32*q2*( s0-cor0)-q3*q0*q32*( s2-cor2))+2.0*(q3*q3)*q32*( q12*( s0-cor0)*q1+q3*q12*( s2-cor2)+q12*( s1-cor1)*q2));
}
}
}
static void samplePointSpatially(const LidarPointCloud& source, int maxNumPointsToUse, double endFractionToIgnore)
{
g_source.clear();
if (maxNumPointsToUse <= 0)
{
g_source = source;
return;
}
// First compute the total "distance" of the point cloud. I.e. the total distance when
// going in order through all the points.
int totalNumPoints = source.size();
double totalDistance = 0.0;
for (int i=1; i<totalNumPoints; ++i)
{
double d = sqrt(Distance2BetweenPoints(source[i-1].targetpos, source[i].targetpos));
totalDistance += d;
}
double endDistanceToIgnore = endFractionToIgnore * totalDistance;
double step = (totalDistance-2.0*endDistanceToIgnore) / maxNumPointsToUse;
double distance = 0.0;
int count = 1;
for (int i=1; i<totalNumPoints; ++i)
{
double d = sqrt(Distance2BetweenPoints(source[i-1].targetpos, source[i].targetpos));
distance += d;
if (distance-endDistanceToIgnore >= count*step &&
distance >= endDistanceToIgnore &&
distance <= (totalDistance-endDistanceToIgnore))
{
g_source.push_back(source.at(i));
++count;
}
}
}
/**
Perform ICP algorithm on source and target points, both of size
n. The optimal translation that maps the source points into target
points is calculated and placed in translation.
*/
void icpGsl(LidarPointCloud& source, double translation[3], double rotation[4], double centerOfRotation[3], int maxNumPointsToUse, double endFractionToIgnore, bool translationOnly, bool rotationOnly)
{
/* For the ICP, we do the following:
First compute an initial transformation between the 2 point sets
by computing the centroid of each set and translating the source
by that amount to the target
Then iterate the following:
1. For each point in source, find the closest point in target.
2. Minimize the ssd between these corresponding points
*/
g_source.clear();
g_target.clear();
g_translationOnly = translationOnly;
g_rotationOnly = rotationOnly;
samplePointSpatially(source, maxNumPointsToUse, endFractionToIgnore);
centroid(g_source, g_sourceCentroid);
// First 3 are translation, next 4 are rotation quaternion
double transformation[7];
transformation[0] = 0.0;
transformation[1] = 0.0;
transformation[2] = 0.0;
transformation[3] = 1.0;
transformation[4] = 0.0;
transformation[5] = 0.0;
transformation[6] = 0.0;
if (rotationOnly == false)
{
// The following finds all the target points using the identity transformation
findAllClosestPoints(transformation);
Point targetCentroid;
centroid(g_target, targetCentroid.targetpos);
// Now update the current translation to be the displacement vector between centroids of the
// source and target points
transformation[0] = targetCentroid.targetpos[0] - g_sourceCentroid[0];
transformation[1] = targetCentroid.targetpos[1] - g_sourceCentroid[1];
transformation[2] = targetCentroid.targetpos[2] - g_sourceCentroid[2];
}
#ifndef NDEBUG
printf("Beginning ICP\n");
printf("Initial value of objective function with no translation: %f\n",
func(transformation, NULL));
#endif
/* Now iterate */
int maxIter = 5000;
int i;
for (i=0;i<maxIter;++i)
{
#ifndef NDEBUG
printf("\nStarting iteration %d of ICP\n", i+1);
#endif
findAllClosestPoints(transformation);
double prevssd = func(transformation, NULL);
// See comment above for why we need to normalize the translation vector
g_MULT_FACTOR = Normalize(transformation);
optimizeGsl(func, grad, transformation, 7, NULL);
MultiplyScalar(transformation, g_MULT_FACTOR);
g_MULT_FACTOR = 1.0;
// Normalize the quaternion since the quaternion probably got denormalized
// during the optimization.
normalizeQuaternion(&transformation[3], &transformation[3]);
double ssd = func(transformation, NULL);
if (ssd >= prevssd)
break;
}
translation[0] = transformation[0];
translation[1] = transformation[1];
translation[2] = transformation[2];
rotation[0] = transformation[3];
rotation[1] = transformation[4];
rotation[2] = transformation[5];
rotation[3] = transformation[6];
centerOfRotation[0] = g_sourceCentroid[0];
centerOfRotation[1] = g_sourceCentroid[1];
centerOfRotation[2] = g_sourceCentroid[2];
// transform the source target points and spacecraft positions using the optimal translation and rotation
int totalNumPoints = source.size();
for (int i=0; i<totalNumPoints; ++i)
{
transformPoint(source[i].targetpos, centerOfRotation, translation, rotation, source[i].targetpos);
transformPoint(source[i].scpos, centerOfRotation, translation, rotation, source[i].scpos);
}
#ifndef NDEBUG
printf("Final transformation: %f %f %f %f %f %f %f\n", translation[0], translation[1], translation[2],
rotation[0], rotation[1], rotation[2], rotation[3]);
#endif
}

View File

@@ -0,0 +1,21 @@
#ifndef __ICP_GSL_H__
#define __ICP_GSL_H__
#include "lidardata.h"
/**
* Run a variant of the Iterative Closest Point algorithm to estimate the optimal translation
* and rotation that best aligns the source points to the surface.
*
* @param[in,out] source source point for which we want to find closest points on body.
* On output, this array contains the translated points
* @param[in] n number of points in source
* @param[in,out] additionalPoints once optimal translation is found, translate these additional points using the optimal translation
* @param[out] translation the optimal translation computed by this function. Assumed to be a 3 element vector.
* @param[out] rotation the optimal rotation computed by this function. Assumed to be a 4 element quaternion.
* @param[out] centerOfRotation the center of rotation of the optimal rotation computed by this function. Assumed to be a 3 element vector.
* @param[out] closestPoints the point on the surface of the body closest to the translated source points
*/
void icpGsl(LidarPointCloud& source, double translation[3], double rotation[4], double centerOfRotation[3], int maxNumPointsToUse, double endFractionToIgnore, bool translationOnly, bool rotationOnly);
#endif

View File

@@ -0,0 +1,252 @@
#include <stdlib.h>
#include <fstream>
#include <iostream>
#include <string>
#include <string.h>
#include "SpiceUsr.h"
#include "optimizer.h"
#include "lidardata.h"
#include "closest-point-vtk.h"
/************************************************************************
* This program optimizes the location of a point cloud by trying to compute the best
* translation that best aligns the point cloud with a shape model. This program
* requires 3 arguments to run in this order:
* 1. objfile - the shape model in OBJ format
* 2. outputfile - the name to be given to the file which will contain the optimal transformation to be applied to the pointt cloud.
* the original point cloud file is not modified
* 3. inputfile - path to input point cloud file to be optimized
************************************************************************/
void saveTransformation(double translation[3],
double rotation[4],
double centerOfRotation[3],
const LidarPointCloud& pointCloudBefore,
const LidarPointCloud& pointCloudAfter,
const std::string& startId,
const std::string& stopId,
const std::string& filename)
{
ofstream fout(filename.c_str());
if (fout.is_open())
{
double minErrorBefore, minErrorAfter;
double maxErrorBefore, maxErrorAfter;
double rmsBefore, rmsAfter;
double meanErrorBefore, meanErrorAfter;
double stdBefore, stdAfter;
computePointCloudStats(pointCloudBefore, minErrorBefore, maxErrorBefore, rmsBefore, meanErrorBefore, stdBefore);
computePointCloudStats(pointCloudAfter, minErrorAfter, maxErrorAfter, rmsAfter, meanErrorAfter, stdAfter);
fout.precision(16);
fout << scientific
<< "{\n"
<< "\"translation\" : [ " << translation[0] << " , " << translation[1] << " , " << translation[2] << " ],\n"
<< "\"rotation\" : [ " << rotation[0] << " , " << rotation[1] << " , " << rotation[2] << " , " << rotation[3] << " ],\n"
<< "\"centerOfRotation\" : [ " << centerOfRotation[0] << " , " << centerOfRotation[1] << " , " << centerOfRotation[2] << " ],\n"
<< "\"startId\" : " << startId << ",\n"
<< "\"stopId\" : " << stopId << ",\n"
<< "\"minErrorBefore\" : " << minErrorBefore << ",\n"
<< "\"maxErrorBefore\" : " << maxErrorBefore << ",\n"
<< "\"rmsBefore\" : " << rmsBefore << ",\n"
<< "\"meanErrorBefore\" : " << meanErrorBefore << ",\n"
<< "\"stdBefore\" : " << stdBefore << ",\n"
<< "\"minErrorAfter\" : " << minErrorAfter << ",\n"
<< "\"maxErrorAfter\" : " << maxErrorAfter << ",\n"
<< "\"rmsAfter\" : " << rmsAfter << ",\n"
<< "\"meanErrorAfter\" : " << meanErrorAfter << ",\n"
<< "\"stdAfter\" : " << stdAfter << "\n"
<< "}\n";
fout.close();
}
else
{
cerr << "Error: Unable to open file '" << filename << "'" << endl;
exit(1);
}
}
static void usage()
{
cout << "This program takes lidar data and a shape model and tries to compute the optimal\n"
<< "translation and rotation that best aligns the lidar data with the shape model.\n"
<< "It implements a variation of the Iterative Closest Point algorithm. The program\n"
<< "supports 2 ways to provide the lidar data:\n"
<< "1. as a text file\n"
<< "2. an SQLite database along with the start and stop id of the lidar points to use\n\n"
<< "Usage: lidar-min-icp [options] <shapefile> <inputfile> <start-id> <stop-id>\n"
<< " <transformationfile>\n\n"
<< "Where:\n"
<< " <shapefile>\n"
<< " Path to shape model in OBJ format which data are optimized to.\n"
<< " <inputfile>\n"
<< " By default this is the path to SQLite database containing lidar data. If\n"
<< " the --load-from-file option is used, then this is the path to a text file\n"
<< " containing the lidar data. The format of the file is as follows. Each line\n"
<< " must contain 7 space separated values. The first is the UTC string of the\n"
<< " the lidar shot. The next 3 are the 3D coordinates of the lidar shot in\n"
<< " kilometers. The final 3 values are the 3D coordinates of the spacecraft\n"
<< " position in kilometers at the time of the lidar shot.\n"
<< " <start-id>\n"
<< " The start id of the lidar shot in the SQLite database. This option is ignored\n"
<< " if the --load-from-file option is specified, but it still must be specified.\n"
<< " <stop-id>\n"
<< " The stop id of the lidar shot in the SQLite database. This option is ignored\n"
<< " if the --load-from-file option is specified, but it still must be specified.\n"
<< " <transformationfile>\n"
<< " Path to file generated by this program upon termination of the optimization\n"
<< " which contains the optimal transformation as a 4x4 matrix that\n"
<< " best aligns the lidar data with the shape model, as well as other error\n"
<< " statistics.\n\n"
<< "Options:\n"
<< " --save <leap-second-kernel>\n"
<< " Save out the lidar data both before and after the optimization to text files.\n"
<< " This option is implied by the --load-from-file option. A SPICE leap second\n"
<< " kernel must be provided in order to convert from ephemeris time to UTC.\n"
<< " --max-number-control-points <int-value>\n"
<< " max number of control points to use when optimizing the lidar data. For\n"
<< " example suppose the actual number of points is 10000 points and you set this\n"
<< " flag to 500. Then when doing the strip adjustment, only 500\n"
<< " of the 10000 points are used to drive the optimization. This can increase\n"
<< " performance significantly without sacrificing the quality of the strip\n"
<< " adjustment. The control points are spread out evenly within the lidar points.\n"
<< " If the max number of control points is greater than the number of lidar\n"
<< " points, then all lidar points are used as control points. A value of 0\n"
<< " means use all lidar points as control points. Default value is 0.\n"
<< " --translation-only\n"
<< " Only estimate a translation that best aligns the points with the model,\n"
<< " not a rotation. By default both a translation and rotation are estimated.\n"
<< " --rotation-only\n"
<< " Only estimate a rotation that best aligns the points with the model,\n"
<< " not a translation. By default both a translation and rotation are estimated.\n"
<< " --load-from-file <leap-second-kernel>\n"
<< " If specified then the second required argument to this program, <inputfile>,\n"
<< " is assumed to refer to a text file containing the lidar points as explained\n"
<< " above, rather than a an SQLite database. On output, the transformed lidar\n"
<< " data are also saved out to a separate file (as if the --save option was\n"
<< " specified). A SPICE leap second kernel must be provided in order to convert\n"
<< " from UTC to ephemeris time.\n"
<< " --end-fraction-to-ignore <value>\n"
<< " Ignore points a specified fraction away from both ends of the window.\n"
<< " Value must be between 0 and 1. Default is 0.\n"
<< " --precision <value>\n"
<< " Number of digits to carry in the initial point cloud positions. Default is 6.\n";
exit(1);
}
int main(int argc, char** argv)
{
bool save = false;
bool translationOnly = false;
bool rotationOnly = false;
bool loadFromFile = false;
std::string leapSecondKernel;
int maxControlPoints = 0;
double endFractionToIgnore = 0.0;
int precision = 6;
int i = 1;
for(; i<argc; ++i)
{
if (!strcmp(argv[i], "--save"))
{
save = true;
leapSecondKernel = argv[++i];
}
else if (!strcmp(argv[i], "--max-number-control-points"))
{
maxControlPoints = atoi(argv[++i]);
}
else if (!strcmp(argv[i], "--translation-only"))
{
translationOnly = true;
}
else if (!strcmp(argv[i], "--rotation-only"))
{
rotationOnly = true;
}
else if (!strcmp(argv[i], "--load-from-file"))
{
loadFromFile = true;
save = true;
leapSecondKernel = argv[++i];
}
else if (!strcmp(argv[i], "--end-fraction-to-ignore"))
{
endFractionToIgnore = atof(argv[++i]);
}
else if (!strcmp(argv[i], "--precision"))
{
precision = atoi(argv[++i]);
}
else
{
break;
}
}
// There must be numRequiredArgs arguments remaining after the options. Otherwise abort.
const int numberRequiredArgs = 5;
if (argc - i != numberRequiredArgs)
usage();
const string shapefile = argv[i++];
const string inputfile = argv[i++];
const string startId = argv[i++];
const string stopId = argv[i++];
const string transformationfile = argv[i++];
initializeVtk(shapefile.c_str());
if (save)
{
furnsh_c(leapSecondKernel.c_str());
}
LidarPointCloud pointCloud;
if (loadFromFile)
pointCloud = loadPointCloudFromFile(inputfile, precision);
else
pointCloud = loadPointCloudSQLite(inputfile, startId, stopId, precision);
if (save)
{
savePointCloudSBMT(pointCloud, transformationfile + "-sbmt-before.txt");
savePointCloudToVTK(pointCloud, transformationfile + "-sbmt-before.vtk");
}
Optimizer optimizer;
optimizer.setPointCloud(pointCloud);
optimizer.setMaxNumberControlPoints(maxControlPoints);
optimizer.setEndFractionToIgnore(endFractionToIgnore);
optimizer.setTranslationOnly(translationOnly);
optimizer.setRotationOnly(rotationOnly);
optimizer.optimize();
double translation[3];
double rotation[4];
double centerOfRotation[3];
optimizer.getOptimalTransformation(translation, rotation, centerOfRotation);
saveTransformation(
translation,
rotation,
centerOfRotation,
pointCloud,
optimizer.getOptimizedPointCloud(),
startId,
stopId,
transformationfile);
if (save)
{
savePointCloudSBMT(optimizer.getOptimizedPointCloud(), transformationfile + "-sbmt-after.txt");
savePointCloudToVTK(optimizer.getOptimizedPointCloud(), transformationfile + "-sbmt-after.vtk");
}
return 0;
}

View File

@@ -0,0 +1,203 @@
#include "lidardata.h"
#include "closest-point-vtk.h"
#include "util.h"
#include "mathutil.h"
#include "SpiceUsr.h"
#include <limits>
#include <fstream>
#include <iostream>
#include <math.h>
#include <stdlib.h>
#include <sqlite3.h>
LidarPointCloud loadPointCloudSQLite(const string& filename,
const string& startId,
const string& stopId,
const int precision)
{
LidarPointCloud referenceTrajectory;
double scale = pow(10, precision);
sqlite3 *db;
sqlite3_stmt *stmt;
/* Open database */
int retval = sqlite3_open_v2(filename.c_str(), &db, SQLITE_OPEN_READONLY, 0);
if(retval)
{
printf("Can't open database: %s\n", sqlite3_errmsg(db));
exit(1);
}
else
{
#ifndef NDEBUG
printf("Opened database successfully\n");
#endif
}
/* Create SQL statement */
const string& query = "SELECT et, xtarget, ytarget, ztarget, xsc, ysc, zsc from lidar where idvalid between " + startId + " and " + stopId;
retval = sqlite3_prepare_v2(db, query.c_str(), -1, &stmt, 0);
#ifndef NDEBUG
printf("Running query: %s\n", query.c_str());
#endif
if(retval)
{
printf("Query Failed, error code: %d\n", retval);
exit(1);
}
while(true)
{
// fetch a row's status
retval = sqlite3_step(stmt);
if(retval == SQLITE_ROW)
{
// SQLITE_ROW means fetched a row
Point p;
p.time = sqlite3_column_double(stmt, 0);
for (int i = 0; i < 3; i++) {
p.targetpos[i] = sqlite3_column_double(stmt, i+1);
p.targetpos[i] = round(p.targetpos[i] * scale) / scale;
p.scpos[i] = sqlite3_column_double(stmt, i+4);
}
referenceTrajectory.push_back(p);
}
else if(retval == SQLITE_DONE)
{
// All rows finished
#ifndef NDEBUG
printf("All rows fetched\n");
#endif
break;
}
else
{
// Some error encountered
printf("Error encountered, error code: %d\n", retval);
exit(1);
}
}
sqlite3_finalize(stmt);
// Close the handle to free memory
sqlite3_close(db);
#ifndef NDEBUG
printf("Finished loading %d points from database\n", (int)referenceTrajectory.size());
#endif
return referenceTrajectory;
}
LidarPointCloud loadPointCloudFromFile(const string& filename, const int precision)
{
LidarPointCloud referenceTrajectory;
double scale = pow(10, precision);
ifstream fin(filename.c_str());
if (fin.is_open())
{
string line;
while (getline(fin, line))
{
Point p;
vector<string> tokens = split(line);
for (int i = 0; i < 3; i++) {
p.targetpos[i] = atof(tokens[i+1].c_str());
p.targetpos[i] = round(p.targetpos[i] * scale) / scale;
p.scpos[i] = atof(tokens[i+4].c_str());
}
utc2et_c(tokens[0].c_str(), &p.time);
referenceTrajectory.push_back(p);
}
fin.close();
}
else
{
cerr << "Error: Unable to open file '" << filename << "'" << endl;
exit(1);
}
return referenceTrajectory;
}
void computePointCloudStats(const LidarPointCloud& pointCloud, double& minError, double& maxError, double& rms, double& meanError, double& std)
{
int size = pointCloud.size();
int found;
minError = std::numeric_limits<double>::max();
maxError = 0.0;
double errorSum = 0.0;
double errorSquaredSum = 0.0;
for (int i=0; i<size; ++i)
{
const Point& p = pointCloud.at(i);
// compute distance to closest point on shape model
double closestPoint[3];
findClosestPointVtk(p.targetpos, closestPoint, &found);
double errorSquared = Distance2BetweenPoints(p.targetpos, closestPoint);
double error = sqrt(errorSquared);
errorSquaredSum += errorSquared;
errorSum += error;
if (error < minError)
minError = error;
if (error > maxError)
maxError = error;
}
rms = sqrt(errorSquaredSum / (double)size);
meanError = errorSum / (double)size;
std = sqrt(errorSquaredSum / (double)size - meanError * meanError);
}
void savePointCloudSBMT(const LidarPointCloud& pointCloud, const std::string& filename)
{
std::ofstream fout(filename.c_str());
char utc[64];
if (fout.is_open())
{
fout.precision(16);
for (unsigned int i=0; i<pointCloud.size(); ++i)
{
const Point& p = pointCloud[i];
et2utc_c(p.time, "ISOC", 6, sizeof(utc), utc);
fout << utc << " "
<< p.targetpos[0] << " "
<< p.targetpos[1] << " "
<< p.targetpos[2] << " "
<< p.scpos[0] << " "
<< p.scpos[1] << " "
<< p.scpos[2] << " "
<< sqrt(Distance2BetweenPoints(p.targetpos, p.scpos))
<< "\n";
}
fout.close();
}
else
{
std::cerr << "Error: Unable to open file '" << filename << "'" << std::endl;
exit(1);
}
}

View File

@@ -0,0 +1,36 @@
#ifndef LIDARDATA_H
#define LIDARDATA_H
#include <string>
#include <iostream>
#include <vector>
using namespace std;
struct Point
{
double time;
double targetpos[3];
double scpos[3];
void print()
{
std::cout << targetpos[0] << " " << targetpos[1] << " " << targetpos[2] << std::endl;
}
};
typedef std::vector<Point> LidarPointCloud;
LidarPointCloud loadPointCloudSQLite(const string& filename,
const string& startId,
const string& stopId,
const int precision);
LidarPointCloud loadPointCloudFromFile(const string& filename, const int precision);
void computePointCloudStats(const LidarPointCloud& pointCloud, double& minError, double& maxError, double& rms, double& meanError, double& std);
void savePointCloudSBMT(const LidarPointCloud& pointCloud, const std::string& filename);
#endif // LIDARDATA_H

View File

@@ -0,0 +1,167 @@
#include <gsl/gsl_multimin.h>
/*-----------------------------------------------------------------------
-------------------------------------------------------------------------
This file contains GSL solver related functions
-------------------------------------------------------------------------
-----------------------------------------------------------------------*/
#define DX 0.00001
struct FuncParam
{
double (*function)(const double*, void* externalParams);
void (*gradient)(const double*, double*, void *externalParams);
void* externalParams;
};
#ifndef NDEBUG
static void printCurrentValue(size_t iter, double fx, const gsl_vector* x, size_t N)
{
printf("Iter: %ld, fx = %.12f", iter, fx);
/*size_t i;
for (i=0; i<N; ++i)
{
printf(", x[%ld] = %f", i, gsl_vector_get(x, i));
}*/
printf("\n");
}
#endif
static double func(const gsl_vector *v, void *internalParams)
{
struct FuncParam* p = (struct FuncParam*)internalParams;
return p->function(gsl_vector_const_ptr(v, 0), p->externalParams);
}
/************************************************************************
* This function numerically computes the gradient of func using finite differences
************************************************************************/
static void grad(const gsl_vector *v,
void *internalParams,
gsl_vector *df)
{
struct FuncParam* p = (struct FuncParam*)internalParams;
if (p->gradient != 0)
{
p->gradient(gsl_vector_const_ptr(v, 0), gsl_vector_ptr(df, 0), p->externalParams);
return;
}
double f = func(v, internalParams);
size_t N = v->size;
size_t i;
size_t j;
for (i=0; i<N; ++i)
{
double coef2[N];
for (j=0; j<N; ++j)
coef2[j] = gsl_vector_get(v,j);
coef2[i] += DX;
double f2 = p->function(coef2, p->externalParams);
gsl_vector_set(df,i, (f2 - f) / DX);
}
}
static void fdf(const gsl_vector *x,
void *params,
double *f,
gsl_vector *df)
{
*f = func(x, params);
grad(x, params, df);
}
/*
Public function
Perform an optimization on provided function with N independent variables.
minimizer contains the initial guess and on output contains the optimal
values of the independent function that minimizes the function.
*/
void optimizeGsl(double (*function)(const double*, void *externalParams),
void (*gradient)(const double*, double*, void *externalParams),
double* minimizer,
size_t N,
void *externalParams)
{
struct FuncParam internalParams;
internalParams.function = function;
internalParams.gradient = gradient;
internalParams.externalParams = externalParams;
size_t iter = 0;
int status;
const gsl_multimin_fdfminimizer_type *T;
gsl_multimin_fdfminimizer *s;
gsl_vector *x;
gsl_multimin_function_fdf my_func;
my_func.n = N;
my_func.f = func;
my_func.df = grad;
my_func.fdf = fdf;
my_func.params = &internalParams;
/* Starting point, x = */
x = gsl_vector_alloc (N);
size_t i;
for (i=0;i<N;++i)
gsl_vector_set (x, i, minimizer[i]);
T = gsl_multimin_fdfminimizer_conjugate_pr;
/*T = gsl_multimin_fdfminimizer_vector_bfgs2;*/
s = gsl_multimin_fdfminimizer_alloc (T, N);
gsl_multimin_fdfminimizer_set (s, &my_func, x, 0.5, 1e-1);
#ifndef NDEBUG
printf("Initial value of objective function: \n");
printCurrentValue(0, function(minimizer, externalParams), x, N);
printf("\n");
#endif
do
{
iter++;
status = gsl_multimin_fdfminimizer_iterate (s);
if (status)
break;
status = gsl_multimin_test_gradient (s->gradient, 1e-1);
#ifndef NDEBUG
if (status == GSL_SUCCESS)
printf ("Minimum found at:\n");
printCurrentValue(iter, s->f, s->x, N);
#endif
}
while (status == GSL_CONTINUE && iter < 50);
for (i=0;i<N;++i)
minimizer[i] = gsl_vector_get (s->x, i);
#ifndef NDEBUG
printf("GSL optimization terminated with status code = %d\n", status);
printCurrentValue(iter, function(minimizer, externalParams), s->x, N);
printf("\n");
#endif
gsl_multimin_fdfminimizer_free (s);
gsl_vector_free (x);
}

View File

@@ -0,0 +1,15 @@
#ifndef OPIMIZEGSL_H
#define OPIMIZEGSL_H
#include <stdio.h>
/* minimizer is both the initial guess and the final returned value */
void optimizeGsl(double (*function)(const double*, void *externalParams),
void (*gradient)(const double*, double*, void *externalParams),
double* minimizer,
size_t N,
void *externalParams);
#endif // OPIMIZEGSL_H

View File

@@ -0,0 +1,72 @@
#include <stdio.h>
#include "optimizer.h"
#include "icp-gsl.h"
#include "mathutil.h"
void Optimizer::setPointCloud(const LidarPointCloud &pointCloud)
{
pointCloud__ = pointCloud;
maxNumberControlPoints__ = 0;
endFractionToIgnore__ = 0.0;
translationOnly__ = false;
rotationOnly__ = false;
}
void Optimizer::setMaxNumberControlPoints(int maxPoints)
{
maxNumberControlPoints__ = maxPoints;
}
void Optimizer::setEndFractionToIgnore(double endFractionToIgnore)
{
endFractionToIgnore__ = endFractionToIgnore;
}
void Optimizer::setTranslationOnly(bool translationOnly)
{
translationOnly__ = translationOnly;
}
void Optimizer::setRotationOnly(bool rotationOnly)
{
rotationOnly__ = rotationOnly;
}
void Optimizer::optimize()
{
#ifndef NDEBUG
printf("Optimizing point cloud with size %d\n", (int)pointCloud__.size());
#endif
optimizedPointCloud__ = pointCloud__;
if (translationOnly__)
icpGsl(optimizedPointCloud__, optimalTranslation__, optimalRotation__, optimalCenterOfRotation__, maxNumberControlPoints__, endFractionToIgnore__, true, false);
else if (rotationOnly__)
icpGsl(optimizedPointCloud__, optimalTranslation__, optimalRotation__, optimalCenterOfRotation__, maxNumberControlPoints__, endFractionToIgnore__, false, true);
else
icpGsl(optimizedPointCloud__, optimalTranslation__, optimalRotation__, optimalCenterOfRotation__, maxNumberControlPoints__, endFractionToIgnore__, false, false);
#ifndef NDEBUG
printf("Finished optimizing point cloud\n\n");
#endif
}
LidarPointCloud Optimizer::getOptimizedPointCloud() const
{
return optimizedPointCloud__;
}
void Optimizer::getOptimalTransformation(double translation[3], double rotation[4], double centerOfRotation[3]) const
{
translation[0] = optimalTranslation__[0];
translation[1] = optimalTranslation__[1];
translation[2] = optimalTranslation__[2];
rotation[0] = optimalRotation__[0];
rotation[1] = optimalRotation__[1];
rotation[2] = optimalRotation__[2];
rotation[3] = optimalRotation__[3];
centerOfRotation[0] = optimalCenterOfRotation__[0];
centerOfRotation[1] = optimalCenterOfRotation__[1];
centerOfRotation[2] = optimalCenterOfRotation__[2];
}

View File

@@ -0,0 +1,51 @@
#ifndef OPTIMIZER_H
#define OPTIMIZER_H
#include "lidardata.h"
/**
* Class for finding the optimal translation of a lidar point cloud that best aligns it with
* a shape model. To use this class, set the point cloud by calling setPointCloud. Then call optimize().
* Finally, you can get the optimized (translated) point cloud by calling getOptimizedPointCloud() or the
* optimal translation by calling getOptimalTranslation.
*/
class Optimizer
{
public:
void setPointCloud(const LidarPointCloud& pointCloud);
void setMaxNumberControlPoints(int maxPoints);
void setEndFractionToIgnore(double endFractionToIgnore);
void setTranslationOnly(bool translationOnly);
void setRotationOnly(bool rotationOnly);
void optimize();
LidarPointCloud getOptimizedPointCloud() const;
void getOptimalTransformation(double translation[3], double rotation[4], double centerOfRotation[3]) const;
private:
LidarPointCloud pointCloud__;
int maxNumberControlPoints__;
double endFractionToIgnore__;
bool translationOnly__;
bool rotationOnly__;
LidarPointCloud optimizedPointCloud__;
double optimalTranslation__[3];
double optimalRotation__[4];
double optimalCenterOfRotation__[3];
};
#endif

86
support-libraries/buildAll.bash Executable file
View File

@@ -0,0 +1,86 @@
#!/bin/bash
ARCH=$(uname -s)_$(uname -m)
if [ -z $1 ]; then
echo "Usage: $0 install_dir"
echo "e.g. $0 3rd-party/"'$(uname -s)_$(uname -m)'
echo -e "\nThis would build everything in 3rd-party/$(uname -s)_$(uname -m) on this machine."
exit 0
fi
install_dir=$1
mkdir -p $install_dir
if [ $? -ne 0 ]; then
echo "cannot create directory $install_dir"
exit 1
fi
if [ ! -w $install_dir ]; then
echo "cannot write to directory $install_dir"
exit 1
fi
pushd $install_dir >/dev/null
install_dir=$(pwd -P)
popd >/dev/null
echo "will install to $install_dir"
set -e
# location of this script
DIR=$(
cd $(dirname $0)
pwd -P
)
build_dir=build
mkdir -p $build_dir
if [ $? -ne 0 ]; then
echo "cannot create directory $build_dir"
exit 1
fi
if [ ! -w $build_dir ]; then
echo "cannot write to directory $build_dir"
exit 1
fi
cd $build_dir
VTK_INSTALL=${install_dir}/vtk
if [ -d ${VTK_INSTALL} ]; then
echo "${VTK_INSTALL} exists. If you want to build VTK, remove this directory and call this script again."
else
${DIR}/vtk/install-vtk.sh $VTK_INSTALL
fi
SPICE_INSTALL=${install_dir}/spice
if [ -d ${SPICE_INSTALL} ]; then
echo "${SPICE_INSTALL} exists. If you want to build SPICE, remove this directory and call this script again."
else
${DIR}/spice/install-spice.sh $SPICE_INSTALL
fi
ALTWG_INSTALL=${install_dir}/altwg
if [ -d ${ALTWG_INSTALL} ]; then
echo "${ALTWG_INSTALL} exists. If you want to build the ALTWG tools, remove this directory and call this script again."
else
${DIR}/altwg/install-altwg.sh $ALTWG_INSTALL
fi
echo -n "looking for ant "
if hash ant 2>/dev/null; then
ant=$(which ant)
echo "... found at $ant."
OPENCV_INSTALL=${install_dir}/opencv
if [ -d ${OPENCV_INSTALL} ]; then
echo "${OPENCV_INSTALL} exists. If you want to build OpenCV, remove this directory and call this script again."
else
${DIR}/opencv/install-opencv.sh $OPENCV_INSTALL
fi
else
echo "... not found. Skipping OpenCV compilation."
fi
${DIR}/util/createInfoFiles/install-createInfoFiles.sh ${install_dir}

View File

@@ -0,0 +1,10 @@
#!/bin/bash
root=SBCLT_support-$(date -u +"%Y.%m.%d")
mkdir -p dist/${root}
rsync -a README.md altwg buildAll.bash spice vtk dist/${root}
cd dist
tar cfz ${root}.tar.gz ./${root}
/bin/rm -fr ./${root}
echo -e "Created ./dist/${root}.tar.gz"

View File

@@ -0,0 +1,51 @@
#!/bin/bash
if [ -z $1 ]; then
ARCH=$(uname -s)_$(uname -m)
echo "Usage: $0 install_dir"
echo "e.g. $0 3rd-party/${ARCH}/opencv"
exit 0
fi
TAG=4.7.0
install_dir=$1
mkdir -p $install_dir
install_dir=$(cd "$install_dir"; pwd -P)
if [ $? -ne 0 ]; then
echo "cannot create directory $install_dir"
exit 1
fi
if [ ! -w $install_dir ]; then
echo "cannot write to directory $install_dir"
exit 1
fi
echo "will install OpenCV to $install_dir"
set -e
BUILD_DIR=buildopencv
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR
cd $BUILD_DIR
curl -L -o opencv-${TAG}.zip https://github.com/opencv/opencv/archive/refs/tags/${TAG}.zip
curl -L -o opencv_contrib-${TAG}.zip https://github.com/opencv/opencv_contrib/archive/refs/tags/${TAG}.zip
unzip -q opencv-${TAG}.zip
unzip -q opencv_contrib-${TAG}.zip
mkdir build
cd build
VTK_DIR=$(dirname $install_dir)/vtk/lib/cmake/vtk-9.2
if [ ! -d ${VTK_DIR} ]; then
unset VTK_DIR
fi
cmake -DCMAKE_BUILD_TYPE=Release -DVTK_DIR=${VTK_DIR} -DCMAKE_INSTALL_PREFIX=$install_dir -DOPENCV_EXTRA_MODULES_PATH=../opencv_contrib-${TAG}/modules -DOPENCV_ENABLE_NONFREE=ON -DBUILD_ZLIB=OFF ../opencv-${TAG}
make -j4
make install

View File

@@ -0,0 +1,102 @@
#!/bin/bash
ARCH=$(uname -s)_$(uname -m)
if [ -z $1 ]; then
echo "Usage: $0 install_dir"
echo "e.g. $0 3rd-party/${ARCH}/spice"
exit 0
fi
install_dir=$1
mkdir -p $install_dir
if [ $? -ne 0 ]; then
echo "cannot create directory $install_dir"
exit 1
fi
if [ ! -w $install_dir ]; then
echo "cannot write to directory $install_dir"
exit 1
fi
pushd $install_dir >/dev/null
install_dir=$(pwd -P)
popd >/dev/null
echo "will install to $install_dir"
set -e
# location of this script
DIR=$(
cd $(dirname $0)
pwd -P
)
SRCDIR=${DIR}/src/${ARCH}
# Install the JNISpice library
if [ ! -e ${SRCDIR}/JNISpice.tar.Z ]; then
mkdir -p ${SRCDIR}
pushd ${SRCDIR} >/dev/null
if [ "$ARCH" == "Darwin_x86_64" ]; then
curl -RO https://naif.jpl.nasa.gov/pub/naif/misc/JNISpice/MacIntel_OSX_AppleC_Java1.8_64bit/packages/JNISpice.tar.Z
elif [ "$ARCH" == "Darwin_arm64" ]; then
if [ -e /project/spice/toolkit/latest/JNISpice.tar.Z ]; then
# preinstalled on srn-devmac1
cp -p /project/spice/toolkit/latest/JNISpice.tar.Z .
else
echo "NAIF SPICE package not found for $ARCH"
fi
elif [ "$ARCH" == "Linux_x86_64" ]; then
curl -RO -N https://naif.jpl.nasa.gov/pub/naif/misc/JNISpice/PC_Linux_GCC_Java1.8_64bit/packages/JNISpice.tar.Z
else
echo "NAIF SPICE package not found for $ARCH"
exit 0
fi
popd >/dev/null
fi
# Get the FORTRAN package
if [ ! -e ${SRCDIR}/toolkit.tar.Z ]; then
mkdir -p ${SRCDIR}
pushd ${SRCDIR} >/dev/null
if [ "$ARCH" == "Darwin_x86_64" ]; then
curl -RO https://naif.jpl.nasa.gov/pub/naif/toolkit/FORTRAN/MacIntel_OSX_gfortran_64bit/packages/toolkit.tar.Z
elif [ "$ARCH" == "Darwin_arm64" ]; then
if [ -e /project/spice/toolkit/latest/toolkit.tar.Z ]; then
# preinstalled on srn-devmac1
cp -p /project/spice/toolkit/latest/toolkit.tar.Z .
else
echo "NAIF SPICE package not found for $ARCH"
fi
elif [ "$ARCH" == "Linux_x86_64" ]; then
curl -RO -N https://naif.jpl.nasa.gov/pub/naif/toolkit//FORTRAN/PC_Linux_gfortran_64bit/packages/toolkit.tar.Z
else
echo "NAIF SPICE package not found for $ARCH"
exit 0
fi
popd >/dev/null
fi
cd $install_dir
if [ -e ${SRCDIR}/toolkit.tar.Z ]; then
rm -rf toolkit
tar xf ${SRCDIR}/toolkit.tar.Z
else
echo "${SRCDIR}/toolkit.tar.Z does not exist"
fi
if [ -e ${SRCDIR}/JNISpice.tar.Z ]; then
rm -rf JNISpice
tar xf ${SRCDIR}/JNISpice.tar.Z
(
cd JNISpice/src/JNISpice
jar cfv ${install_dir}/spice.jar spice
)
else
echo "${SRCDIR}/JNISpice.tar.Z does not exist"
fi

View File

View File

@@ -0,0 +1,74 @@
# SPICE
SPICE_DIR = /project/sbmtpipeline/software/spice/cspice-20220921/cspice
SPICE_LIB = $(SPICE_DIR)/lib/cspice.a
#
# CFITSIO
#CFITSIO = cfitsio
#CFITSIO_LIBS = -L./cfitsio -lcfitsio
#LIB_CFITSIO = cfitsio/libcfitsio.a
CFITSIO =
CFITSIO_LIBS =
LIB_CFITSIO =
#
# Compiler
CXX = g++
# Change -O2 to -O0 -g for debugging
COPT = -O2
CFLAGS = -Wall --pedantic -Wno-long-long
CINCLUDES = -I$(SPICE_DIR)/include -I.
CLIBS = $(SPICE_LIB)
#
# Main tool (createInfoFiles).
TOOL = createInfoFiles
SRC = createInfoFiles.cpp getSpacecraftState.cpp getTargetState.cpp getFov.cpp saveInfoFile.cpp
OBJ = $(SRC:.cpp=.o)
# SPICE pointing tool (computePointing).
SPICE_TOOL = computePointing
SPICE_SRC = computePointing.cpp getSpacecraftState.cpp getTargetState.cpp getFov.cpp saveInfoFile.cpp
SPICE_OBJ = $(SPICE_SRC:.cpp=.o)
$(TOOL): $(OBJ)
$(CXX) -o $@ $(COPT) $(CFLAGS) $(CINCLUDES) $(OBJ) $(CLIBS)
$(SPICE_TOOL): $(CFITSIO) $(SPICE_OBJ)
$(CXX) -o $@ $(COPT) $(CFLAGS) $(CINCLUDES) $(SPICE_OBJ) $(CLIBS)
all: $(CFITSIO) $(TOOL) $(SPICE_TOOL)
cfitsio: $(LIB_CFITSIO)
cfitsio/libcfitsio.a:
@echo "Configuring cfitsio prior to building..."
@(cd cfitsio; ./configure > configure.txt 2>&1; \
if test $$? -ne 0; then \
echo "Errors occurred; see file cfitsio/configure.txt for more info." >&2; \
exit 1; \
else \
echo "done"; \
fi)
@echo "Building cfitsio..."
@(cd cfitsio; make libcfitsio.a >& make.txt 2>&1 ; \
if test $$? -ne 0; then \
echo "Errors occurred; see file cfitsio/make.txt for more info." >&2; \
exit 1; \
else \
echo "done"; \
fi)
clean:
rm -f *.o
@(if test -f cfitsio/Makefile; then cd cfitsio; make clean > /dev/null 2>&1; fi)
distclean:
rm -f *.o
rm -f $(TOOL) $(SPICE_TOOL)
rm -rf *.dSYM/
@(if test -f cfitsio/Makefile; then cd cfitsio; make distclean > /dev/null 2>&1; fi)
.SUFFIXES:
.SUFFIXES: .cpp .o
.cpp.o:
$(CXX) -c -o $@ $(COPT) $(CFLAGS) $(CINCLUDES) $<

View File

@@ -0,0 +1,470 @@
#include "computePointing.hpp"
#include "SpiceUsr.h"
#include <algorithm>
#include <iostream>
#include <iterator>
#include <map>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
namespace {
const char * const g_programName = "computeInstrumentPointing";
int g_status = 0;
enum CLParId {
BODY,
FRAME,
HELP,
// IMAGE,
INSTRUMENT,
MK,
OUT_FILE,
QUIET,
SC_ID,
TIME,
NUMBER_PARS
};
const char * const CLParams[] = {
"b",
"f",
"h",
// "img",
"i",
"m",
"o",
"q",
"s",
"t"
};
SpiceChar ERROR_REPORT[] = "SHORT";
SpiceChar RETURN[] = "RETURN";
}
namespace spice_pointing {
class InvalidCommandLine : public std::runtime_error {
public:
InvalidCommandLine(const std::string & what): std::runtime_error(what) {}
InvalidCommandLine(const InvalidCommandLine & source): std::runtime_error(source) {}
};
struct CommandLinePar {
CommandLinePar(const std::string & key): m_key(key), m_value(), m_isDefined(false), m_isKey(false) {}
CommandLinePar(const std::string & key, const std::string & value, bool isValueKeywordName):
m_key(key), m_value(value), m_isDefined(true), m_isKey(isValueKeywordName) {}
CommandLinePar(const CommandLinePar & source): m_key(source.m_key), m_value(source.m_value),
m_isDefined(source.m_isDefined), m_isKey(source.m_isKey) {}
CommandLinePar & operator=(const CommandLinePar & source) {
m_key = source.m_key;
m_value = source.m_value;
m_isDefined = source.m_isDefined;
m_isKey = source.m_isKey;
return *this;
}
std::string m_key;
std::string m_value;
bool m_isDefined;
bool m_isKey;
};
class CommandLineParameters {
public:
typedef std::map<CLParId, CommandLinePar *> MapType;
CommandLineParameters(const MapType * pars): m_pars(pars) {}
virtual ~CommandLineParameters() {
if (m_pars != 0) {
for (MapType::const_reverse_iterator itor = m_pars->rbegin(); itor != m_pars->rend(); ++itor) {
delete itor->second;
}
delete m_pars;
}
}
const CommandLinePar& operator[](CLParId parId) const {
return *m_pars->at(parId);
}
private:
const MapType * const m_pars;
};
CommandLineParameters * interpretCommandLine(int argc, char ** argv);
void computeSpicePointing(const CommandLineParameters & pars);
void usage(std::ostream & ios);
SpiceDouble parseET(const std::string & time);
bool isSpiceError();
// namespace spice_pointing
////////////////////////////////////////////////////////////////////////////////
}
std::ostream & operator<<(std::ostream & os, const spice_pointing::CommandLinePar & par) {
os << "Par \"" << par.m_key << "\" "
<< (par.m_isDefined ? "= \"" + par.m_value + "\"" : "(undefined)");
return os;
}
namespace {
std::string trim(const std::string & s) {
std::string::const_iterator begin(s.begin());
std::string::const_iterator end(s.end());
while (begin != end && std::isspace(*begin)) {
++begin;
}
while (begin != end && std::isspace(*end)) {
--end;
}
return std::string(begin, end);
}
void reportMissingPar(const spice_pointing::CommandLinePar & par, std::stringstream & ss, std::string & delim) {
if (!par.m_isDefined) {
ss << delim << "-" << par.m_key;
// if (par.m_key != CLParams[HELP] && {par.m_key != CLParams[IMAGE]) {
// ss << " (or -k" << par.m_key << ")";
// }
delim = ", ";
}
}
bool extractPar(spice_pointing::CommandLinePar & par, const std::string & arg) {
spice_pointing::CommandLinePar newPar(par);
bool foundMatch = false;
std::string flag("-" + par.m_key + "=");
std::string::size_type flagSize(flag.size());
// Look for -par=value.
if (arg.compare(0, flagSize, flag) == 0) {
newPar.m_value = arg.substr(flagSize);
newPar.m_isDefined = true;
foundMatch = true;
// } else if (par.m_key != CLParams[HELP] && par.m_key != CLParams[IMAGE]) {
// // Don't accept keyword option for either help or image file name inputs.
//
// // Look for -kpar=value (keyword).
// flag = "-k" + par.m_key + "=";
// flagSize = flag.size();
//
// if (arg.compare(0, flagSize, flag) == 0) {
// newPar.m_value = arg.substr(flagSize);
// newPar.m_isDefined = true;
// newPar.m_isKey = true;
// foundMatch = true;
// }
}
if (!foundMatch) {
// Look for -par (boolean parameters, e.g., -h for help/usage).
flag = "-" + par.m_key;
if (arg.compare(flag) == 0) {
newPar.m_value = "true";
newPar.m_isDefined = true;
foundMatch = true;
}
}
if (foundMatch) {
if (par.m_isDefined) {
// Check for duplicate parameters.
std::stringstream ss;
ss << "duplicated parameter? Attempted to parse parameter \"" + arg + "\" into already-defined parameter " << par;
throw spice_pointing::InvalidCommandLine(ss.str());
}
par = newPar;
}
return foundMatch;
}
void extractPar(spice_pointing::CommandLinePar & par, int argc, char ** argv) {
for (int i = 1; i < argc; ++i) {
extractPar(par, argv[i]);
}
}
bool isTrue(const spice_pointing::CommandLinePar & par) {
using namespace std;
if (par.m_isDefined) {
string parString(par.m_value);
transform(parString.begin(), parString.end(), parString.begin(), ::tolower);
return parString == "t" || parString == "true";
}
return false;
}
// namespace
////////////////////////////////////////////////////////////////////////////////
}
namespace spice_pointing {
CommandLineParameters * interpretCommandLine(int argc, char ** argv) {
using namespace std;
for (int i = 0; i < argc; ++i) {
}
CommandLineParameters::MapType * pars = new CommandLineParameters::MapType();
bool isKey = false;
for (int i = 0; i < NUMBER_PARS; ++i) {
const CLParId id = static_cast<CLParId>(i);
const string & key(CLParams[i]);
CommandLinePar * par = new CommandLinePar(key);
// extractPar(*par, argc, argv);
isKey |= par->m_isKey;
pars->insert(make_pair(id, par));
}
for (char ** arg = argv + 1; arg < argv + argc; ++arg) {
bool foundMatch = false;
for (int i = 0; i < NUMBER_PARS; ++i) {
const CLParId id = static_cast<CLParId>(i);
if (extractPar(*pars->at(id), *arg)) {
foundMatch = true;
// Do NOT break out of loop here so extractPar may find any duplicated parameters.
}
}
if (!foundMatch) {
throw InvalidCommandLine(string("no match for command line parameter \"") + *arg + "\"");
}
}
if (!isTrue(*pars->at(HELP))) {
// Validate all parameters prior to run if user did not request showing usage.
stringstream ss;
string delim;
// const CommandLinePar * imageFile = pars->at(IMAGE);
//
// if (isKey && !imageFile->m_isDefined) {
// reportMissingPar(*imageFile, ss, delim);
// ss << " (image file required if using any -k options)";
// }
reportMissingPar(*pars->at(BODY), ss, delim);
reportMissingPar(*pars->at(FRAME), ss, delim);
reportMissingPar(*pars->at(INSTRUMENT), ss, delim);
reportMissingPar(*pars->at(MK), ss, delim);
reportMissingPar(*pars->at(SC_ID), ss, delim);
reportMissingPar(*pars->at(TIME), ss, delim);
string missing = ss.str();
if (!missing.empty()) {
throw InvalidCommandLine("missing required parameter(s): " + missing);
}
}
return new CommandLineParameters(pars);
}
void computeSpicePointing(const CommandLineParameters & pars) {
using namespace std;
const char * body(pars[BODY].m_value.c_str());
const char * frame(pars[FRAME].m_value.c_str());
const char * instr(pars[INSTRUMENT].m_value.c_str());
const char * mkFile(pars[MK].m_value.c_str());
const char * scId(pars[SC_ID].m_value.c_str());
const char * time(pars[TIME].m_value.c_str());
furnsh_c(mkFile);
if (isSpiceError()) {
stringstream ss;
ss << "unable to initialize SPICE environment using metakernel file \"" << mkFile << "\"";
throw runtime_error(ss.str());
}
double et = parseET(time);
if (isSpiceError()) {
stringstream ss;
ss << "unable to parse time \"" << time << "\"";
throw runtime_error(ss.str());
}
double scPosition[3];
double unused[3];
double boredir[3];
double updir[3];
double frustum[12];
double sunPosition[3];
getSpacecraftState(et, scId, body, frame, scPosition, unused);
if (isSpiceError()) {
stringstream ss;
ss << "unable to get position of spacecraft " << scId << " in the " << frame << " frame";
throw runtime_error(ss.str());
}
getTargetState(et, scId, body, frame, "SUN", sunPosition, unused);
if (isSpiceError()) {
stringstream ss;
ss << "unable to get the sun position in the " << frame << " frame";
throw runtime_error(ss.str());
}
getFov(et, scId, body, frame, instr, boredir, updir, frustum);
if (isSpiceError()) {
stringstream ss;
ss << "unable to get the " << instr << " FOV in the " << frame << " frame";
throw runtime_error(ss.str());
}
const CommandLinePar & outFile = pars[OUT_FILE];
if (outFile.m_isDefined) {
const char * outFileString(outFile.m_value.c_str());
saveInfoFile(outFileString, time, scPosition, boredir, updir, frustum, sunPosition);
}
if (!isTrue(pars[QUIET])) {
writeInfo(cout, time, scPosition, boredir, updir, frustum, sunPosition);
}
}
void usage(std::ostream & ios) {
std::stringstream ss;
ss
<< "--------------------------------------------------------------------------------"
<< "\nTool: " << g_programName
<< "\n--------------------------------------------------------------------------------"
<< "\nDescription: from a collection of SPICE kernels specified using a single SPICE"
<< "\n metakernel file, compute SPICE pointing and FOV information for an instrument"
<< "\n on a spacecraft with respect to a body in a particular frame at a particular"
<< "\n moment of time. Returns 0 for success and non-0 if a problem occurs setting"
<< "\n up SPICE, loading kernels, or computing the SPICE pointing information. The"
<< "\n computed information may be displayed and/or written to an output INFO file."
<< "\n"
<< "\n This tool may be used simply to test whether a given set of inputs would"
<< "\n return a valid SPICE pointing. To do this, do not specify an output file (no"
<< "\n -o parameter), use -q to suppress the output if desired, and check the"
<< "\n tool's exit value."
<< "\n--------------------------------------------------------------------------------"
<< "\nUsage: " << g_programName << " <parameters>"
<< "\n where <parameters> are:"
<< "\n"
<< "\n -m=<metakernel> (required), <metakernel> is the full path to the"
<< "\n metakernel file used to load all SPICE kernels"
<< "\n -s=<spacecraft-name> (required), <spacecraft-name> is the name of"
<< "\n the spacecraft as referenced in the SPICE kernels"
<< "\n -i=<instrument-name> (required), <instrument-name> is the name of the"
<< "\n instrument as referenced in the SPICE kernels"
<< "\n -b=<body> (required), <body> is the name of the body used by SPICE as the"
<< "\n origin from which to compute the pointing"
<< "\n -f=<frame> (required), <frame> is the name of the SPICE frame in which to"
<< "\n compute the SPICE pointing"
<< "\n -t=<time> (required), <time> is the (UTC) time at which to compute the"
<< "\n pointing"
<< "\n -o=<output-file> (optional), <output-file> is the full path to an output"
<< "\n INFO file; if not specified, no output file will be written"
<< "\n -q | -q=<quiet> (optional), suppress displaying computed info if the first"
<< "\n form is used, or if in the second form <quiet> equals t or true (case"
<< "\n insensitive); note that error messages are never suppressed"
<< "\n -h | -h=<flag> (optional), show this usage message and exit if the first"
<< "\n form is used, or if in the second form <flag> equals t or true"
<< "\n (case insensitive)"
<< "\n--------------------------------------------------------------------------------"
<< std::endl;
ios << ss.str();
}
SpiceDouble parseET(const std::string & time) {
SpiceDouble et;
str2et_c(time.c_str(), &et);
if (isSpiceError()) {
throw std::runtime_error("unable to parse an ET from the string " + time);
}
return et;
}
bool isSpiceError() {
bool error = failed_c() != 0;
reset_c();
return error;
}
// namespace spice_pointing
////////////////////////////////////////////////////////////////////////////////
}
int main(int argc, char ** argv) {
using namespace spice_pointing;
using namespace std;
// Return rather than abort after a SPICE error so the code may take additional actions.
erract_c("SET", 1, RETURN);
// Make mssages shorter -- but SPICE seems to ignore this.
errprt_c("SET", 1, ERROR_REPORT);
CommandLineParameters * pars = 0;
try {
pars = interpretCommandLine(argc, argv);
if (isTrue((*pars)[HELP])) {
usage(cout);
} else {
computeSpicePointing(*pars);
}
} catch (const InvalidCommandLine & e) {
usage(cerr);
cerr << "ERROR (invalid command line): " << e.what() << endl;
g_status = 1;
} catch (const exception & e) {
cerr << "ERROR: " << e.what() << endl;
g_status = 1;
} catch (...) {
cerr << "Unspecified ERROR" << endl;
g_status = 1;
}
try {
delete pars;
} catch (const exception & e) {
cerr << "ERROR during clean-up: " << e.what() << endl;
g_status = 1;
} catch (...) {
cerr << "Unspecified ERROR during clean-up." << endl;
g_status = 1;
}
return g_status;
}

View File

@@ -0,0 +1,14 @@
#ifndef computePointing_hpp
#define computePointing_hpp
#include <iosfwd>
#include <string>
void getTargetState (double et, const char* spacecraft, const char* body, const char* bodyFrame, const char* targetBody, double targetpos[3], double velocity[3]);
void getSpacecraftState(double et, const char* spacecraft, const char* body, const char* bodyFrame, double scPosition[3], double velocity[3]);
void getFov (double et, const char* spacecraft, const char* body, const char* bodyFrame, const char* instrFrame, double boredir[3], double updir[3], double frustum[12]);
void saveInfoFile(const std::string & filename, const std::string & utc, const double scposb[3], const double boredir[3], const double updir[3], const double frustum[12], const double sunpos[3]);
void writeInfo(std::ostream & os, const std::string & utc, const double scposb[3], const double boredir[3], const double updir[3], const double frustum[12], const double sunpos[3]);
#endif

View File

@@ -0,0 +1,227 @@
#include "computePointing.hpp"
#include <algorithm>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>
#include "SpiceUsr.h"
using namespace std;
// ******************************************************************
// Generic package to generate SPICE pointing info files. FITS header
// information is not used.
// ******************************************************************
// The following 3 functions were adapted from
// http://stackoverflow.com/questions/479080/trim-is-not-part-of-the-standard-c-c-library?rq=1
const std::string whiteSpaces(" \f\n\r\t\v");
// Remove initial and trailing whitespace from string. Modifies string in-place
inline string trimRight(std::string& str, const std::string& trimChars =
whiteSpaces) {
std::string::size_type pos = str.find_last_not_of(trimChars);
str.erase(pos + 1);
return str;
}
inline string trimLeft(std::string& str, const std::string& trimChars =
whiteSpaces) {
std::string::size_type pos = str.find_first_not_of(trimChars);
str.erase(0, pos);
return str;
}
inline string trim(std::string& str, const std::string& trimChars = whiteSpaces) {
trimRight(str, trimChars);
trimLeft(str, trimChars);
return str;
}
vector<pair<string, string> > loadFileList(const string& filelist) {
ifstream fin(filelist.c_str());
vector < pair<string, string> > files;
if (fin.is_open()) {
string line;
while (fin.good()) {
getline(fin, line);
if (trim(line).size() == 0) {
continue;
}
stringstream ss(line);
vector<string> splitLine;
while (ss.good()) {
string segment;
getline(ss, segment, ',');
splitLine.push_back(segment);
}
if (splitLine.size() != 2) {
throw out_of_range("Each line must have a file name and image time, separated by commas.");
}
files.push_back(make_pair(trim(splitLine[0]), trim(splitLine[1])));
}
} else {
cerr << "Error: Unable to open file '" << filelist << "'" << endl;
throw runtime_error("Can't open file " + filelist);
}
return files;
}
/*
Taken from Amica create_info_files.cpp and LORRI create_info_files.cpp
and then heavily modified.
This program creates an info file for each fit file. For example the
file N2516167681.INFO is created for the fit file N2516167681.FIT.
This program takes the following input arguments:
1. metakernel - a SPICE meta-kernel file containing the paths to the kernel files
2. body - IAU name of the target body, all caps
3. bodyFrame - Typically IAU_<body>, but could be something like RYUGU_FIXED
4. spacecraft - SPICE spacecraft name
5. instrumentname - SPICE instrument name, NOT the instrument FRAME, as used to be true
6. imageTimeStampFile - path to CSV file in which all image files are listed (relative
to "topDir") with their UTC time stamps
7. infoDir - path to output directory where infofiles should be saved to
8. imageListFile - path to output file in which all image files for which an infofile was
created (image file name only, not full path) will be listed along with
their start times.
9. imageListFullPathFile - path to output file in which all image files for which an infofile
was created will be listed (full path relative to the server directory).
10. missingInfoList - path to output file in which all image files for which no infofile
was created will be listed, preceded with a string giving the cause for
why no infofile could be created.
*/
int main(int argc, char** argv)
{
if (argc < 11)
{
cerr << "Usage: createInfoFiles <metakernel> <body> <bodyFrame> <spacecraft> <instrument> "
"<imageTimeStampFile> <infoDir> <imageListFile> <imageListFullPathFile> <missingInfoList>" << endl;
return 1;
}
int argIndex = 0;
string metakernel = argv[++argIndex];
const char* body = argv[++argIndex];
const char* bodyFrame = argv[++argIndex];
const char* scid = argv[++argIndex];
const char* instr = argv[++argIndex];
string inputfilelist = argv[++argIndex];
string infofileDir = argv[++argIndex];
string imageListFile = argv[++argIndex];
string imageListFullPathFile = argv[++argIndex];
string missingInfoList = argv[++argIndex];
cout << "Initializing SPICE with metakernel " << metakernel << endl;
furnsh_c(metakernel.c_str());
cout << "Furnished SPICE files" << endl;
// Do ignore errors because otherwise we don't get back much information.
erract_c("SET", 1, (char*)"RETURN");
vector< pair<string, string> > fitfiles;
try {
fitfiles = loadFileList(inputfilelist);
} catch (exception e) {
cerr << "Error while trying to load file list: " << e.what() << endl;
return 1;
}
// Output list of missing info files.
ofstream missingInfoStream(missingInfoList.c_str());
if (!missingInfoStream.is_open()) {
cerr << "Error: Unable to open file used to log missing info files " << missingInfoList.c_str() << " for writing" << endl;
return 1;
}
cout << "File to log missing info files: " << missingInfoList << endl;
//Image list
ofstream fout(imageListFile.c_str());
if (!fout.is_open()) {
cerr << "Error: Unable to open file " << imageListFile << " for writing" << endl;
return 1;
}
ofstream foutFullPath(imageListFullPathFile.c_str());
if (!foutFullPath.is_open()) {
cerr << "Error: Unable to open file " << imageListFullPathFile << " for writing" << endl;
return 1;
}
cout << "Processing image list of size " << fitfiles.size() << endl;
for (unsigned int i = 0; i < fitfiles.size(); ++i) {
reset_c();
pair < string, string > fileWithTime = fitfiles[i];
string fileName = fileWithTime.first;
const char *utc = fileWithTime.second.c_str();
double et;
double scposb[3];
SpiceDouble unused[3];
double boredir[3];
double updir[3];
double frustum[12];
double sunPosition[3];
utc2et_c(utc, &et);
if (failed_c()) {
missingInfoStream << "Unable to get ET for image file "
<< fileName << endl;
continue;
}
getSpacecraftState(et, scid, body, bodyFrame, scposb, unused);
getTargetState(et, scid, body, bodyFrame, "SUN", sunPosition, unused);
getFov(et, scid, body, bodyFrame, instr, boredir, updir, frustum);
if (failed_c()) {
missingInfoStream << "Unable to get pointing for image file " << fileName << endl;
continue;
}
foutFullPath << fileName << endl;
size_t last_slash_idx = fileName.find_last_of("\\/");
if (std::string::npos != last_slash_idx) {
fileName.erase(0, last_slash_idx + 1);
}
size_t last_dot_idx = fileName.find_last_of(".");
if (std::string::npos == last_dot_idx) {
last_dot_idx = fileName.size();
}
string infofilename = infofileDir + "/"
+ fileName.substr(0, last_dot_idx) + ".INFO";
try {
saveInfoFile(infofilename, utc, scposb, boredir, updir, frustum,
sunPosition);
cout << "created " << infofilename << endl;
} catch (exception e) {
break;
}
fout << fileName << " " << utc << endl;
}
cout << "done." << endl;
missingInfoStream.close();
// If errors did occur, at least exit with a non-0 status.
if (failed_c()) {
return 1;
}
return 0;
}

View File

@@ -0,0 +1,217 @@
#include <stdio.h>
#include <iostream>
#include <string>
#include "SpiceUsr.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
extern "C"
{
#include "SpiceUsr.h"
}
#define FILSIZ 256
#define LNSIZE 81
#define MAXCOV 100000
#define WINSIZ ( 2 * MAXCOV )
#define TIMLEN 51
#define MAXBND 4
#define WDSIZE 32
using namespace std;
/*
This function computes the instrument boresight and frustum vectors in the observer body frame
at the time the spacecraft imaged the body.
Input:
et: Ephemeris time when an image of the body was taken
observerBody: Name of observer body (e.g. EROS, PLUTO, PHOEBE)
bodyFrame: The body frame, usually in the form of IAU_<observerBody>, but can also be like RYUGU_FIXED (in Ryugu's case)
spacecraft: Name of the spacecraft that took the image
instrName: SPICE instrument name on the observing spacecraft
Output:
boredir: Boresight direction in bodyframe coordinates
updir:
frustum: Field of view boundary corner vectors in bodyframe coordinates
*/
void getFov(double et, const char* spacecraft, const char* observerBody, const char* bodyFrame, const char* instrName, double boredir[3], double updir[3], double frustum[12])
{
double lt, notUsed[6];
// double targpos[3]; // sc to target vector in j2000
// double scpos[3]; // target to sc vector in j2000
double inst2inert[3][3], inert2bf[3][3], inst2bf[3][3];
char shape[WDSIZE];
char frame[WDSIZE];
double bsight [3];
int n;
double bounds [MAXBND][3];
double boundssbmt [MAXBND][3];
// The celestial body is the target when dealing with light time
// This is specific to RYUGU - which needs RYUGU_FIXED ----- This really should be passed in and not dealt with here (e.g. a restriction on the arguments - do a check to make sure it is valid)
// string bodyFrame = observerBody + string("_FIXED");
// This is general body frame - e.g. IAU_BENNU
// string bodyFrame = string("IAU_") + observerBody;
const char* abcorr = "LT+S";
const char* inertframe = "J2000";
SpiceInt instid;
double tmpvec[3];
SpiceChar instrFrame[WDSIZE];
getfvn_c(instrName, MAXBND, WDSIZE, WDSIZE, shape, instrFrame, bsight, &n, bounds);
if (failed_c()) {
cerr << "Failed getfvn call to get the instrument frame name for instrument " << instrName << endl;
return;
}
/*
* Compute the apparent position of the center of the observer body
* as seen from the spacecraft at the epoch of observation (et),
* and the one-way light time from the observer to the spacecraft.
* Only the returned light time will be used from this call, as
* such, the reference frame does not matter here. Use J2000.
*/
// spkpos_c(target, et, inertframe, abcorr, obs, targpos, &lt);
spkpos_c(observerBody, et, inertframe, abcorr, spacecraft, notUsed, &lt);
if (failed_c()) {
cerr << "Failed getFov call to spkpos for frame " << instrFrame << endl;
return;
}
/*
* Get field of view boresight and boundary corners
*/
SpiceBoolean found = SPICEFALSE;
bodn2c_c(instrName, &instid, &found);
if (failed_c() || found != SPICETRUE) {
cerr << "Failed bodn2c for instrument " << instrName << endl;
return;
}
getfov_c(instid, MAXBND, WDSIZE, WDSIZE, shape, frame, bsight, &n, bounds);
if (failed_c()) {
cerr << "Failed getfov for frame " << instrFrame << ", which returned id " << instid << endl;
return;
}
//Tested with POLYCAM. The correct values are returned.
// cout<< "bs " << bsight[0] << ", " << bsight[1] << ", " << bsight[2] << endl;
// cout<< "bdy1 " << bounds[0][1] << ", " << bounds[0][1] << ", " << bounds[0][2] << endl;
// cout<< "bdy1 " << bounds[1][1] << ", " << bounds[1][1] << ", " << bounds[1][2] << endl;
// cout<< "bdy1 " << bounds[2][1] << ", " << bounds[2][1] << ", " << bounds[2][2] << endl;
// cout<< "bdy1 " << bounds[3][1] << ", " << bounds[3][1] << ", " << bounds[3][2] << endl;
/*
* Get the coordinate transformation from instrument to
* inertial frame at time ET
*/
pxform_c(instrFrame, inertframe, et, inst2inert);
if (failed_c()) {
cerr << "Failed pxform1" << endl;
return;
}
/*
//The values calculated below are not used, they are just
//for comparing with FITS header attitude (debugging).
double sc2j[3][3];
pxform_c("ORX_SPACECRAFT", inertframe, et, sc2j);
SpiceDouble q[4];
m2q_c(sc2j, q);
cout << "sc2j " << sc2j[0][0] << " " << sc2j[0][1] << " " << sc2j[0][2] << endl;
cout << "sc2j " << sc2j[1][0] << " " << sc2j[1][1] << " " << sc2j[1][2] << endl;
cout << "sc2j " << sc2j[2][0] << " " << sc2j[2][1] << " " << sc2j[2][2] << endl;
//end debugging code.
*/
/*
* Get the coordinate transformation from inertial to
* body-fixed coordinates at ET minus one light time ago.
* This subtraction is neccessary because the body is the
* observer in SBMT, but et is the time at the spacecraft.
* The light time here is the time it takes light to travel
* between the body and the spacecraft.
*/
pxform_c(inertframe, bodyFrame, et - lt, inert2bf);
if (failed_c()) {
cerr << "Failed pxform2" << endl;
return;
}
/*
* Compute complete transformation to go from
* instrument-fixed coordinates to body-fixed coords
*/
mxm_c(inert2bf, inst2inert, inst2bf);
/*
* Get the coordinate transformation from instrument to
* body-fixed coordinates at ET minus one light time ago.
* This subtraction is neccessary because the body is the
* observer in SBMT, but et is the time at the spacecraft.
* The light time here is the time it takes light to travel
* between the body and the spacecraft.
* SEEING IF THIS CAN REPLACE THE TWO PXFORM CALLS ABOVE.
* I DON'T THINK SO, THE VALUES DO DIFFER SLIGHTLY.
*/
// pxform_c(instrFrame, bodyFrame, et - lt, inst2bf);
// if (failed_c()) {
// cerr << "Failed pxform2" << endl;
// return;
// }
//swap the boundary corner vectors so they are in the correct order for SBMT
//getfov returns them in the following order (quadrants): I, II, III, IV.
//SBMT expects them in the following order (quadrants): II, I, III, IV.
//So the vector index mapping is
//SBMT SPICE
// 0 1
// 1 0
// 2 2
// 3 3
boundssbmt[0][0] = bounds[1][0];
boundssbmt[0][1] = bounds[1][1];
boundssbmt[0][2] = bounds[1][2];
boundssbmt[1][0] = bounds[0][0];
boundssbmt[1][1] = bounds[0][1];
boundssbmt[1][2] = bounds[0][2];
boundssbmt[2][0] = bounds[2][0];
boundssbmt[2][1] = bounds[2][1];
boundssbmt[2][2] = bounds[2][2];
boundssbmt[3][0] = bounds[3][0];
boundssbmt[3][1] = bounds[3][1];
boundssbmt[3][2] = bounds[3][2];
//transform boresight into body frame.
mxv_c(inst2bf, bsight, boredir);
//transform boundary corners into body frame and pack into frustum array.
int k = 0;
for (int i=0; i<MAXBND; i++)
{
double bdyCorner[3];
double bdyCornerBodyFrm[3];
vpack_c(boundssbmt[i][0], boundssbmt[i][1], boundssbmt[i][2], bdyCorner);
mxv_c(inst2bf, bdyCorner, bdyCornerBodyFrm);
for (int j=0; j<3; j++)
{
frustum[k] = bdyCornerBodyFrm[j];
k++;
}
}
/* Then compute the up direction */
vpack_c(1.0, 0.0, 0.0, tmpvec);
mxv_c(inst2bf, tmpvec, updir);
}

View File

@@ -0,0 +1,59 @@
#include <stdio.h>
#include <stdexcept>
#include <limits>
#include <iostream>
#include <string>
#include "SpiceUsr.h"
using namespace std;
/*
This function computes the state (position and velocity) of the spacecraft in the observer body frame,
at the time the spacecraft imaged the body.
Input:
et: Ephemeris time when the image was taken
observerBody: Name of observer body (e.g. EROS, PLUTO)
bodyFrame: The body frame, usually in the form of IAU_<observerBody>, but can also be like RYUGU_FIXED (in Ryugu's case)
spacecraft: NAIF SPICE name of spacecraft (e.g. NEAR, NH). These can be found at
https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/req/naif_ids.html
Output:
bodyToSc: The position of the spacecraft in observer body-fixed coordinates corrected for light time
velocity: The velocity of the spacecraft in observer body-fixed coordinates corrected for light time
*/
void getSpacecraftState(double et, const char* spacecraft, const char* observerBody, const char* bodyFrame, double bodyToSc[3], double velocity[3])
{
double lt, scToBodyState[6];
const char* abcorr = "LT+S";
//string bodyFrame = observerBody + string("_FIXED");
//string bodyFrame = string("IAU_") + observerBody;
// string bodyFrame = "RYUGU_FIXED";
/*
* Compute the apparent state of the body as seen from the
* spacecraft at the epoch of observation, in the body-fixed
* frame, corrected for stellar aberration and light time.
* Note that the time entered is the time at the spacecraft,
* who is the observer.
*/
spkezr_c(observerBody, et, bodyFrame, abcorr, spacecraft, scToBodyState, &lt);
if (failed_c()) {
cerr << "Failed getSpacecraftState call to spkezr for body frame observerbody " << observerBody << " body frame " << bodyFrame << " at time " << et << endl;
return;
}
/*
* The state of the spacecraft (apparent position and velocity)
* relative to the body is just the negative of this state. Note
* that this is not the same as the apparent position and velocity
* of the spacecraft as seen from the body at time et, because et
* is the time at the spacecraft not the body.
*/
bodyToSc[0] = -scToBodyState[0];
bodyToSc[1] = -scToBodyState[1];
bodyToSc[2] = -scToBodyState[2];
velocity[0] = -scToBodyState[3];
velocity[1] = -scToBodyState[4];
velocity[2] = -scToBodyState[5];
}

View File

@@ -0,0 +1,71 @@
#include <stdio.h>
#include <iostream>
#include <string>
#include "SpiceUsr.h"
using namespace std;
#define TIMFMT "YYYY-MON-DD HR:MN:SC.###::UTC (UTC)"
#define TIMLEN 41
/*
This function computes the position of the target in the observer body frame, at the time the spacecraft observed the body.
Input:
et: Ephemeris time when an image of the body was taken
spacecraft: Name of the spacecraft that took the image
observerBody: Name of observer body (e.g. EROS, PLUTO, PHOEBE)
bodyFrame: The body frame, usually in the form of IAU_<observerBody>, but can also be like RYUGU_FIXED (in Ryugu's case)
targetBody: Name of target body (e.g. SUN, EARTH). SPICE names can be found at
https://naif.jpl.nasa.gov/pub/naif/toolkit_docs/FORTRAN/req/naif_ids.html
Output:
bodyToSc: The position of the target in observer body-fixed coordinates corrected for light time
velocity: The velocity of the target in observer body-fixed coordinates corrected for light time
*/
void getTargetState(double et, const char* spacecraft, const char* observerBody, const char* bodyFrame, const char* targetBody, double bodyToTarget[3], double velocity[3])
{
double lt, notUsed[6], bodyToTargetState[6];
const char* abcorr = "LT+S";
//string bodyFrame = observerBody + string("_FIXED");
//string bodyFrame = string("IAU_") + observerBody;
// string bodyFrame = "RYUGU_FIXED";
/*
* Compute the apparent state of the center of the observer body
* as seen from the spacecraft at the epoch of observation (et),
* and the one-way light time from the observer to the spacecraft.
* Only the returned light time will be used from this call, as
* such, the reference frame does not matter here. Use the body
* fixed frame.
*/
spkezr_c(observerBody, et, bodyFrame, abcorr, spacecraft, notUsed, &lt);
if (failed_c()) {
cerr << "Failed spkezr" << endl;
return;
}
/*
* Back up the time at the observer body by the light time to the
* spacecraft. This is the time that light from the target body was
* received at the observer body when the spacecraft took the image.
* It is the time at the observer body. Now simply get the position
* of the target at this time, as seen from the observer body, in
* the observer body frame.
*/
spkezr_c(targetBody, et - lt, bodyFrame, abcorr, observerBody, bodyToTargetState, &lt);
if (failed_c()) {
cerr << "Failed spkezr" << endl;
return;
}
/*
* Assign the output variables.
*/
bodyToTarget[0] = bodyToTargetState[0];
bodyToTarget[1] = bodyToTargetState[1];
bodyToTarget[2] = bodyToTargetState[2];
velocity[0] = bodyToTargetState[3];
velocity[1] = bodyToTargetState[4];
velocity[2] = bodyToTargetState[5];
}

View File

@@ -0,0 +1,41 @@
#!/bin/bash
ARCH=$(uname -s)_$(uname -m)
if [ -z $1 ]; then
echo "Usage: $0 install_dir"
echo "e.g. $0 arch/${ARCH}/cfitsio"
exit 0
fi
install_dir=$1
# location of this script
DIR=$(cd $(dirname $0); pwd -P)
SRCDIR=${DIR}
cd ${SRCDIR}
make all SPICE_DIR="${install_dir}/spice/JNISpice" > make-all.txt 2>&1
if test $? -ne 0; then
echo "Problem building createInfoFiles; see file ${SRCDIR}/make-all.txt"
exit 1
fi
make clean > /dev/null 2>&1
rm -f make-all.txt
mkdir -p ${install_dir}/bin
mv createInfoFiles ${install_dir}/bin
if test $? -ne 0; then
echo "Problem installing createInfoFiles"
exit 1
fi
mv computePointing ${install_dir}/bin
if test $? -ne 0; then
echo "Problem installing computePointing"
exit 1
fi
make distclean > /dev/null 2>&1

View File

@@ -0,0 +1,67 @@
#include "computePointing.hpp"
#include <exception>
#include <fstream>
#include <iostream>
#include <stdexcept>
using namespace std;
void saveInfoFile(const string & filename, const string & utc, const double scposb[3], const double boredir[3], const double updir[3], const double frustum[12], const double sunpos[3]) {
ofstream fout(filename.c_str());
if (!fout.is_open())
{
cerr << "Error: Unable to open file " << filename << " for writing" << endl;
throw runtime_error("Can't open file " + filename);
}
writeInfo(fout, utc, scposb, boredir, updir, frustum, sunpos);
}
void writeInfo(ostream & os, const string & utc, const double scposb[3], const double boredir[3], const double updir[3], const double frustum[12], const double sunpos[3]) {
os.precision(16);
os << "START_TIME = " << utc << "\n";
os << "STOP_TIME = " << utc << "\n";
os << "SPACECRAFT_POSITION = ( ";
os << scientific << scposb[0] << " , ";
os << scientific << scposb[1] << " , ";
os << scientific << scposb[2] << " )\n";
os << "BORESIGHT_DIRECTION = ( ";
os << scientific << boredir[0] << " , ";
os << scientific << boredir[1] << " , ";
os << scientific << boredir[2] << " )\n";
os << "UP_DIRECTION = ( ";
os << scientific << updir[0] << " , ";
os << scientific << updir[1] << " , ";
os << scientific << updir[2] << " )\n";
os << "FRUSTUM1 = ( ";
os << scientific << frustum[0] << " , ";
os << scientific << frustum[1] << " , ";
os << scientific << frustum[2] << " )\n";
os << "FRUSTUM2 = ( ";
os << scientific << frustum[3] << " , ";
os << scientific << frustum[4] << " , ";
os << scientific << frustum[5] << " )\n";
os << "FRUSTUM3 = ( ";
os << scientific << frustum[6] << " , ";
os << scientific << frustum[7] << " , ";
os << scientific << frustum[8] << " )\n";
os << "FRUSTUM4 = ( ";
os << scientific << frustum[9] << " , ";
os << scientific << frustum[10] << " , ";
os << scientific << frustum[11] << " )\n";
os << "SUN_POSITION_LT = ( ";
os << scientific << sunpos[0] << " , ";
os << scientific << sunpos[1] << " , ";
os << scientific << sunpos[2] << " )\n";
}

View File

@@ -0,0 +1,212 @@
#!/bin/bash
# Use this script to build VTK on a Mac, Linux, or Windows 64-bit
# system. On all platforms, the JAVA_HOME environmental variable must
# be defined to equal the path where the JDK is installed.
#
# Platform specific notes:
#
# Linux:
# - Install gcc.
#
# Mac:
# - Install the latest XCode.
#
# Windows:
# - You must run this script from Cygwin. Install Cygwin's curl.
# - Do NOT install Cygwin's CMake. Instead download and install the Windows
# version from www.cmake.org.
# - Install Visual Studio 2019
# This is the tag to download from https://gitlab.kitware.com/hococoder/vtk
BUILD_VERSION=9.2.6
TAG=v${BUILD_VERSION}-apl
VTK_VERSION=vtk-${TAG}
ARCH=$(uname -s)_$(uname -m)
if [ -z $1 ]; then
echo "Usage: $0 install_dir"
echo "e.g. $0 3rd-party/${ARCH}/vtk"
exit 0
fi
install_dir=$1
mkdir -p $install_dir
if [ $? -ne 0 ]; then
echo "cannot create directory $install_dir"
exit 1
fi
if [ ! -w $install_dir ]; then
echo "cannot write to directory $install_dir"
exit 1
fi
pushd $install_dir > /dev/null
install_dir=$(pwd -P)
popd > /dev/null
echo "will install VTK to $install_dir"
set -e
# location of this script
SCRIPTDIR=$(cd $(dirname $0); pwd -P)
echo "SCRIPTDIR IS $SCRIPTDIR"
# current working directory
DIR=$(pwd -P)
echo "DIR is $DIR"
: ${JAVA_HOME:?"Error: JAVA_HOME environmental variable not set"}
BUILD_DIR=buildvtk
rm -rf $BUILD_DIR
mkdir -p $BUILD_DIR
cd $BUILD_DIR
echo git clone --branch ${TAG} https://gitlab.kitware.com/hococoder/vtk.git ${VTK_VERSION}
git clone --branch ${TAG} https://gitlab.kitware.com/hococoder/vtk.git ${VTK_VERSION}
platform=$(uname)
nativejar="-natives-windows-amd64"
if [ $platform == "Darwin" ];then
nativejar="-natives-macosx-universal"
elif [ $platform == "Linux" ];then
nativejar="-natives-linux-amd64"
fi
jogljars=""
if [ $VTK_VERSION != "vtk-v${BUILD_VERSION}-apl" ]; then
for jar in "" $nativejar; do
for prefix in jogl-all gluegen-rt; do
filename=${SCRIPTDIR}/src/${prefix}${jar}.jar
jogljars="$filename $jogljars"
if [ ! -e $filename ]; then
pushd ${SCRIPTDIR}/src
wget -N https://jogamp.org/deployment/v2.3.2/jar/$(basename ${filename})
popd
fi
ln -s $filename $(basename $filename)
done
done
else
for jar in "-v2.4.0-rc4"; do
echo "jar is ${jar}"
for prefix in jogl-all gluegen-rt; do
filename=${SCRIPTDIR}/resources/${prefix}${jar}.jar
jogljars="$filename $jogljars"
if [ ! -e $filename ]; then
echo "${filename} is not in the resources folder, please fix and rerun"
fi
ln -sfn $filename ${DIR}/${BUILD_DIR}/${prefix}.jar
done
done
for jar in $nativejar; do
echo "jar is ${jar}"
for prefix in jogl-all gluegen-rt; do
filename=${SCRIPTDIR}/resources/${prefix}${jar}.jar
jogljars="$filename $jogljars"
if [ ! -e $filename ]; then
echo "${filename} is not in the resources folder, please fix and rerun"
fi
ln -s $filename $(basename $filename)
done
done
fi
mkdir -p build
mkdir -p install
# Required when building with modern Java versions - note this still gives a module warning
JAVA_VERSION=17
BUILD_OPTIONS="-DVTK_JAVA_SOURCE_VERSION=${JAVA_VERSION} -DVTK_JAVA_TARGET_VERSION=${JAVA_VERSION} -DCMAKE_BUILD_TYPE=Release"
INSTALL_DIR=$(pwd -P)/install
GLUE_PATH=${DIR}/${BUILD_DIR}/gluegen-rt.jar
JOGL_PATH=${DIR}/${BUILD_DIR}/jogl-all.jar
CXX_FLAGS=
C_FLAGS=
if [ "$(uname)" == "Darwin" ]; then
echo "building VTK for macOS"
BUILD_SUFFIX=""
if [ "$(uname -m)" == "arm64" ]; then
BUILD_SUFFIX="arm64-${BUILD_VERSION}"
fi
echo "The build suffix is ${BUILD_SUFFIX}"
BUILD_OPTIONS="${BUILD_OPTIONS}
-DCMAKE_POLICY_VERSION_MINIMUM=3.5
-Wno-dev
-DVTK_CUSTOM_LIBRARY_SUFFIX=${BUILD_SUFFIX}
-DVTK_REQUIRED_OBJCXX_FLAGS:STRING=
-DVTK_USE_COCOA=ON
-DJAVA_AWT_INCLUDE_PATH:PATH=${JAVA_HOME}/include
-DJAVA_AWT_LIBRARY:FILEPATH=${JAVA_HOME}/lib/libjawt.dylib
-DJAVA_INCLUDE_PATH:PATH=${JAVA_HOME}/include
-DJAVA_INCLUDE_PATH2:PATH=${JAVA_HOME}/include/darwin
-DJAVA_JVM_LIBRARY:FILEPATH=${JAVA_HOME}/lib/server/libjvm.dylib
-DJava_IDLJ_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/idlj
-DJava_JARSIGNER_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/jarsigner
-DJava_JAR_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/jar
-DJava_JAVAC_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/javac
-DJava_JAVADOC_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/javadoc
-DJava_JAVAH_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/javah
-DJava_JAVA_EXECUTABLE:FILEPATH=${JAVA_HOME}/bin/java"
elif [ "$(uname)" == "Linux" ]; then
echo "building VTK for Linux"
CXX_FLAGS="-DCMAKE_CXX_FLAGS_RELEASE:String=-O2 -DNDEBUG $CXX_FLAGS"
C_FLAGS="-DCMAKE_C_FLAGS_RELEASE:String=-O2 -DNDEBUG $CFLAGS"
else
echo "building VTK for Cygwin"
BUILD_OPTIONS="-GVisual Studio 16 2019"
INSTALL_DIR=`cygpath -w $INSTALL_DIR`
GLUE_PATH=`cygpath -w $GLUE_PATH`
JOGL_PATH=`cygpath -w $JOGL_PATH`
fi
cd build
cmake $BUILD_OPTIONS \
-DCMAKE_INSTALL_PREFIX:PATH=${INSTALL_DIR} \
-DBUILD_SHARED_LIBS:BOOL=ON \
-DBUILD_TESTING:BOOL=OFF \
-DVTK_DEBUG_LEAKS:BOOL=OFF \
-DVTK_WRAP_JAVA:BOOL=ON \
-DVTK_JAVA_JOGL_COMPONENT:BOOL=ON \
-DJOGL_GLUE:FILEPATH=${GLUE_PATH} \
-DJOGL_LIB:FILEPATH=${JOGL_PATH} \
-DVTK_DATA_EXCLUDE_FROM_ALL=ON \
-DVTK_WRAP_PYTHON=OFF \
-DVTK_MODULE_ENABLE_VTK_hdf5=NO \
../${VTK_VERSION}
cmake --build . --config Release --target install -j4
BUILD_DIR=$(pwd -P)
cd ../install
if [ -d lib64 ]; then
ln -sf lib64 lib
fi
for jogljar in $jogljars; do
for dir in lib lib64; do
if [ -d $dir/java ]; then
cp -p $jogljar $dir/java
fi
done
done
rsync -avP . $install_dir/
echo "installed VTK. You may remove ${BUILD_DIR}."