Skip to content
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ ADD_SUBDIRECTORY(wave_geography)
# Documentation
SET(WAVE_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
SET(WAVE_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR})
SET(WAVE_MODULES utils geometry containers benchmark controls kinematics matching vision optimization gtsam geography)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I see, it's a regression from a previous commit where I deleted the LIBWAVE_MODULES list.

Is it possible to have the WAVE_ADD_MODULE function add to this list instead of writing it out here?

ADD_SUBDIRECTORY(docs)

# This is where .cmake files will be installed (default under share/)
Expand Down
7 changes: 3 additions & 4 deletions docs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,11 @@ project(wave_docs)
set(WAVE_DOXY_INPUT
"${WAVE_SOURCE_DIR}/README.md ${CMAKE_CURRENT_SOURCE_DIR}/ref")

foreach(dir ${LIBWAVE_MODULES})
include_directories(${dir}/include)
set(WAVE_DOXY_INPUT "${WAVE_DOXY_INPUT} ${WAVE_SOURCE_DIR}/${dir}")
foreach(module ${WAVE_MODULES})
include_directories(wave_${module}/include)
set(WAVE_DOXY_INPUT "${WAVE_DOXY_INPUT} ${WAVE_SOURCE_DIR}/wave_${module}")
endforeach()


find_package(Doxygen)
if(DOXYGEN_FOUND)
configure_file(Doxyfile.in ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile @ONLY)
Expand Down
4 changes: 1 addition & 3 deletions wave_geography/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@ PROJECT(wave_geography)
WAVE_ADD_MODULE(${PROJECT_NAME}
DEPENDS
Eigen3::Eigen
GeographicLib
SOURCES
src/world_frame_conversions.cpp)
GeographicLib)

# Unit tests
IF(BUILD_TESTING)
Expand Down
225 changes: 164 additions & 61 deletions wave_geography/include/wave/geography/world_frame_conversions.hpp
Original file line number Diff line number Diff line change
@@ -1,47 +1,18 @@
/* Copyright (c) 2017, Waterloo Autonomous Vehicles Laboratory (WAVELab),
* Waterloo Intelligent Systems Engineering Lab (WISELab),
* University of Waterloo.
*
* Refer to the accompanying LICENSE file for license information.
*
* ############################################################################
******************************************************************************
| |
| /\/\__/\_/\ /\_/\__/\/\ |
| \ \____/ / |
| '----________________----' |
| / \ |
| O/_____/_______/____\O |
| /____________________\ |
| / (#UNIVERSITY#) \ |
| |[**](#OFWATERLOO#)[**]| |
| \______________________/ |
| |_""__|_,----,_|__""_| |
| ! ! ! ! |
| '-' '-' |
| __ _ _ _____ ___ __ _ ___ _ _ ___ ___ ____ ____ |
| / \ | | | ||_ _|/ _ \| \| |/ _ \| \ / |/ _ \/ _ \ / | |
| / /\ \ | |_| | | | ||_||| |\ |||_||| \/ |||_||||_|| \===\ |==== |
| /_/ \_\|_____| |_| \___/|_| \_|\___/|_|\/|_|\___/\___/ ____/ |____ |
| |
******************************************************************************
* ############################################################################
*
* File: world_frame_conversions.hpp
* Desc: Header file for world frame conversion functions
* Auth: Michael Smart <michael.smart@uwaterloo.ca>
*
* ############################################################################
*/
/** @file
* @ingroup geography
*/

#ifndef WAVE_GEOGRAPHY_WORLD_FRAME_CONVERSIONS_HPP
#define WAVE_GEOGRAPHY_WORLD_FRAME_CONVERSIONS_HPP

#include <cmath>
#include <Eigen/Core>
#include <GeographicLib/Geocentric.hpp>
#include <GeographicLib/LocalCartesian.hpp>

namespace wave {
/** @addtogroup geography
* @{ */

/** Converts a point from LLH (Latitude [deg], Longitude [deg], Height[m]) to
* ECEF.
Expand All @@ -50,7 +21,21 @@ namespace wave {
* is relative to the WGS84 ellipsoid.
* @param[out] ecef the corresponding point in the geocentric ECEF frame.
*/
void ecefPointFromLLH(const double llh[3], double ecef[3]);
template <typename DerivedA, typename DerivedB>
void ecefPointFromLLH(const Eigen::MatrixBase<DerivedA> &llh,

@leokoppel leokoppel Aug 20, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eigen::MatrixBase<DerivedB> const &ecef) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedB, 3);

double latitude = llh(0), longitude = llh(1), height = llh(2);

GeographicLib::Geocentric earth = GeographicLib::Geocentric::WGS84();

double X, Y, Z;
earth.Forward(latitude, longitude, height, X, Y, Z);

const_cast<Eigen::MatrixBase<DerivedB> &>(ecef) << X, Y, Z;
};

/** Converts a point from LLH (Latitude [deg], Longitude [deg], Height[m]) to
* ECEF.
Expand All @@ -59,41 +44,107 @@ void ecefPointFromLLH(const double llh[3], double ecef[3]);
* @param[out] llh the corresponding llh point as (Latitude, Longitude,
* Height). Height is relative to the WGS84 ellipsoid.
*/
void llhPointFromECEF(const double ecef[3], double llh[3]);
template <typename DerivedA, typename DerivedB>
void llhPointFromECEF(const Eigen::MatrixBase<DerivedA> &ecef,
Eigen::MatrixBase<DerivedB> const &llh) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedB, 3);

/** Computes the 3D Affine transform from ECEF to a local datum-defined ENU
* frame as a 4x4 row-major matrix.
double X = ecef(0), Y = ecef(1), Z = ecef(2);

GeographicLib::Geocentric earth = GeographicLib::Geocentric::WGS84();

double latitude, longitude, height;
earth.Reverse(X, Y, Z, latitude, longitude, height);

const_cast<Eigen::MatrixBase<DerivedB> &>(llh) << latitude, longitude,
height;
};

/** Computes the 3D Affine transform from a local datum-defined ENU frame to
* ECEF as a 4x4 row-major matrix.
*
* @param[in] datum the LLH datum point defining the transform. If
* /p datum_is_LLH is set to false, then the datum values are taken as ECEF
* instead.
* @param[out] T_enu_ecef the 4x4 row-major transformation matrix converting
* column-vector points from ECEF to the local ENU frame defined by the datum
* point.
* @param[out] T_ecef_enu the 4x4 row-major transformation matrix converting
* column-vector points from the local ENU frame defined by the datum point to
* ECEF.
* @param[in] datum_is_llh \b true: The given datum values are LLH
* (default). <BR>
* \b false: The given datum values are ECEF
*/
void enuFromECEFTransformMatrix(const double datum[3],
double T_enu_ecef[4][4],
bool datum_is_llh = true);
template <typename DerivedA, typename DerivedB>
void ecefFromENUTransformMatrix(const Eigen::MatrixBase<DerivedA> &datum,

@leokoppel leokoppel Aug 20, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm these function interfaces are (already were) really confusing. This may be a good chance to change the names as well..

Whenever I see ecefFromENUTransformMatrix it seems like the function takes an "ENU transform matrix" and returns an "ecef".

It is also confusing to have a parameter datum_is_llh change the meaning of another parameter.

Maybe two separate functions
ecefEnuTransformFromEcef
ecefEnuTransformFromLlh

where the second function does the if (datum_is_llh) code and calls the first

@leokoppel leokoppel Aug 20, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually if the ecefPointFromLLH function is changed to return the point, there is no need for the second function (where datum_is_llh) at all. The user can just chain the calls, ecefEnuTransformFromEcef(ecefPointFromLLH(x))

Eigen::MatrixBase<DerivedB> const &T_ecef_enu,
bool datum_is_llh = true) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedB, 4, 4);

/** Computes the 3D Affine transform from a local datum-defined ENU frame to
* ECEF as a 4x4 row-major matrix.
// Both Forward() and Reverse() return the same rotation matrix from ENU
// to ECEF
std::vector<double> R_ecef_enu(9, 0.0);
GeographicLib::Geocentric earth = GeographicLib::Geocentric::WGS84();
double datum_X, datum_Y, datum_Z;

if (datum_is_llh) {
double latitude = datum(0), longitude = datum(1), height = datum(2);

earth.Forward(
latitude, longitude, height, datum_X, datum_Y, datum_Z, R_ecef_enu);
} else {
// Datum is already given in ECEF
datum_X = datum(0);
datum_Y = datum(1);
datum_Z = datum(2);

double latitude, longitude, height;
earth.Reverse(
datum_X, datum_Y, datum_Z, latitude, longitude, height, R_ecef_enu);
}

const_cast<Eigen::MatrixBase<DerivedB> &>(T_ecef_enu) << R_ecef_enu[0],
R_ecef_enu[1], R_ecef_enu[2], datum_X, R_ecef_enu[3], R_ecef_enu[4],
R_ecef_enu[5], datum_Y, R_ecef_enu[6], R_ecef_enu[7], R_ecef_enu[8],
datum_Z, 0.0, 0.0, 0.0, 1.0;
};

/** Computes the 3D Affine transform from ECEF to a local datum-defined ENU
* frame as a 4x4 row-major matrix.
*
* @param[in] datum the LLH datum point defining the transform. If
* /p datum_is_LLH is set to false, then the datum values are taken as ECEF
* instead.
* @param[out] T_ecef_enu the 4x4 row-major transformation matrix converting
* column-vector points from the local ENU frame defined by the datum point to
* ECEF.
* @param[out] T_enu_ecef the 4x4 row-major transformation matrix converting
* column-vector points from ECEF to the local ENU frame defined by the datum
* point.
* @param[in] datum_is_llh \b true: The given datum values are LLH
* (default). <BR>
* \b false: The given datum values are ECEF
*/
void ecefFromENUTransformMatrix(const double datum[3],
double T_ecef_enu[4][4],
bool datum_is_llh = true);
template <typename DerivedA, typename DerivedB>
void enuFromECEFTransformMatrix(const Eigen::MatrixBase<DerivedA> &datum,
Eigen::MatrixBase<DerivedB> const &T_enu_ecef,
bool datum_is_llh = true) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_MATRIX_SPECIFIC_SIZE(DerivedB, 4, 4);

// Get T_ecef_enu and then invert it
Eigen::Matrix4d T_ecef_enu;
ecefFromENUTransformMatrix(datum, T_ecef_enu, datum_is_llh);

// Affine inverse: [R | t]^(-1) = [ R^T | - R^T * t]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice this is way shorter than before!

(const_cast<Eigen::MatrixBase<DerivedB> &>(T_enu_ecef))
.topLeftCorner(3, 3) = T_ecef_enu.topLeftCorner(3, 3).transpose();
// Affine inverse translation component: -R_inverse * b
// with b as the 4th column of T_ecef_enu
(const_cast<Eigen::MatrixBase<DerivedB> &>(T_enu_ecef))
.topRightCorner(3, 1) = -T_ecef_enu.topLeftCorner(3, 3).transpose() *
T_ecef_enu.topRightCorner(3, 1);
(const_cast<Eigen::MatrixBase<DerivedB> &>(T_enu_ecef)).row(3) =
Eigen::MatrixXd::Zero(1, 4);
(const_cast<Eigen::MatrixBase<DerivedB> &>(T_enu_ecef))(3, 3) = 1.0;
};

/** Converts a source point from LLH to a target ENU point in the local
* Cartesian ENU frame defined by the provided datum.
Expand All @@ -107,10 +158,36 @@ void ecefFromENUTransformMatrix(const double datum[3],
* (default). <BR>
* \b false: The given datum values are ECEF
*/
void enuPointFromLLH(const double point_llh[3],
const double enu_datum[3],
double point_enu[3],
bool datum_is_llh = true);
template <typename DerivedA, typename DerivedB, typename DerivedC>
void enuPointFromLLH(const Eigen::MatrixBase<DerivedA> &point_llh,
const Eigen::MatrixBase<DerivedB> &enu_datum,
Eigen::MatrixBase<DerivedC> const &point_enu,
bool datum_is_llh = true) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedB, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedC, 3);

Eigen::Vector3d enu_datum_llh;
if (datum_is_llh) {
enu_datum_llh(0) = enu_datum(0);
enu_datum_llh(1) = enu_datum(1);
enu_datum_llh(2) = enu_datum(2);
} else {
// Datum is ECEF
llhPointFromECEF(enu_datum, enu_datum_llh);
}

GeographicLib::Geocentric earth = GeographicLib::Geocentric::WGS84();
GeographicLib::LocalCartesian localENU(
enu_datum_llh(0), enu_datum_llh(1), enu_datum_llh(2), earth);

localENU.Forward(point_llh(0),
point_llh(1),
point_llh(2),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_enu))(0),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_enu))(1),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_enu))(2));
};

/** Converts a source point from ENU in the local Cartesian ENU frame
* defined by the provided datum to a target LLH point.
Expand All @@ -124,10 +201,36 @@ void enuPointFromLLH(const double point_llh[3],
* (default). <BR>
* \b false: The given datum values are ECEF
*/
void llhPointFromENU(const double point_enu[3],
const double enu_datum[3],
double point_llh[3],
bool datum_is_llh = true);
template <typename DerivedA, typename DerivedB, typename DerivedC>
void llhPointFromENU(const Eigen::MatrixBase<DerivedA> &point_enu,
const Eigen::MatrixBase<DerivedB> &enu_datum,
Eigen::MatrixBase<DerivedC> const &point_llh,
bool datum_is_llh = true) {
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedA, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedB, 3);
EIGEN_STATIC_ASSERT_VECTOR_SPECIFIC_SIZE(DerivedC, 3);
Eigen::Vector3d enu_datum_llh;
if (datum_is_llh) {
enu_datum_llh(0) = enu_datum(0);
enu_datum_llh(1) = enu_datum(1);
enu_datum_llh(2) = enu_datum(2);
} else {
// Datum is ECEF
llhPointFromECEF(enu_datum, enu_datum_llh);
}

GeographicLib::Geocentric earth = GeographicLib::Geocentric::WGS84();
GeographicLib::LocalCartesian localENU(
enu_datum_llh(0), enu_datum_llh(1), enu_datum_llh(2), earth);

localENU.Reverse(point_enu(0),
point_enu(1),
point_enu(2),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_llh))(0),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_llh))(1),
(const_cast<Eigen::MatrixBase<DerivedC> &>(point_llh))(2));
};

/** @} group geography */
} // namespace wave
#endif // WAVE_GEOGRAPHY_WORLD_FRAME_CONVERSIONS_HPP
Loading