/* * Copyright (C) 2012 Open Source Robotics Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include #include "gazebo/common/Console.hh" #include "gazebo/common/SphericalCoordinates.hh" #include "test/util.hh" using namespace gazebo; class SphericalCoordinatesTest : public gazebo::testing::AutoLogFixture { }; ////////////////////////////////////////////////// // Test different constructors, default parameters TEST_F(SphericalCoordinatesTest, Constructor) { // Default surface type common::SphericalCoordinates::SurfaceType st = common::SphericalCoordinates::EARTH_WGS84; // No arguments, default parameters { common::SphericalCoordinates sc; EXPECT_EQ(sc.GetSurfaceType(), st); EXPECT_EQ(sc.LatitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.LongitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.HeadingOffset(), ignition::math::Angle()); EXPECT_NEAR(sc.GetElevationReference(), 0.0, 1e-6); } // SurfaceType argument, default parameters { common::SphericalCoordinates sc(st); EXPECT_EQ(sc.GetSurfaceType(), st); EXPECT_EQ(sc.LatitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.LongitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.HeadingOffset(), ignition::math::Angle()); EXPECT_NEAR(sc.GetElevationReference(), 0.0, 1e-6); } // All arguments { ignition::math::Angle lat(0.3), lon(-1.2), heading(0.5); double elev = 354.1; common::SphericalCoordinates sc(st, lat, lon, elev, heading); EXPECT_EQ(sc.GetSurfaceType(), st); EXPECT_EQ(sc.LatitudeReference(), lat); EXPECT_EQ(sc.LongitudeReference(), lon); EXPECT_EQ(sc.HeadingOffset(), heading); EXPECT_NEAR(sc.GetElevationReference(), elev, 1e-6); } } ////////////////////////////////////////////////// // SurfaceType Convert function TEST_F(SphericalCoordinatesTest, Convert) { // Default surface type common::SphericalCoordinates::SurfaceType st = common::SphericalCoordinates::EARTH_WGS84; EXPECT_EQ(common::SphericalCoordinates::Convert("EARTH_WGS84"), st); } ////////////////////////////////////////////////// // Test Set functions TEST_F(SphericalCoordinatesTest, SetFunctions) { // Default surface type common::SphericalCoordinates::SurfaceType st = common::SphericalCoordinates::EARTH_WGS84; // Default parameters common::SphericalCoordinates sc; EXPECT_EQ(sc.GetSurfaceType(), st); EXPECT_EQ(sc.LatitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.LongitudeReference(), ignition::math::Angle()); EXPECT_EQ(sc.HeadingOffset(), ignition::math::Angle()); EXPECT_NEAR(sc.GetElevationReference(), 0.0, 1e-6); { ignition::math::Angle lat(0.3), lon(-1.2), heading(0.5); double elev = 354.1; sc.SetSurfaceType(st); sc.SetLatitudeReference(lat); sc.SetLongitudeReference(lon); sc.SetHeadingOffset(heading); sc.SetElevationReference(elev); EXPECT_EQ(sc.GetSurfaceType(), st); EXPECT_EQ(sc.LatitudeReference(), lat); EXPECT_EQ(sc.LongitudeReference(), lon); EXPECT_EQ(sc.HeadingOffset(), heading); EXPECT_NEAR(sc.GetElevationReference(), elev, 1e-6); } } ////////////////////////////////////////////////// // Test coordinate transformations TEST_F(SphericalCoordinatesTest, CoordinateTransforms) { // Default surface type common::SphericalCoordinates::SurfaceType st = common::SphericalCoordinates::EARTH_WGS84; { // Parameters ignition::math::Angle lat(0.3), lon(-1.2), heading(ignition::math::Angle::HalfPi); double elev = 354.1; common::SphericalCoordinates sc(st, lat, lon, elev, heading); // Check GlobalFromLocal with heading offset of 90 degrees { // local frame ignition::math::Vector3d xyz; // east, north, up ignition::math::Vector3d enu; xyz.Set(1, 0, 0); enu = sc.GlobalFromLocal(xyz); EXPECT_NEAR(enu.Y(), xyz.X(), 1e-6); EXPECT_NEAR(enu.X(), -xyz.Y(), 1e-6); EXPECT_EQ(xyz, sc.LocalFromGlobal(enu)); xyz.Set(0, 1, 0); enu = sc.GlobalFromLocal(xyz); EXPECT_NEAR(enu.Y(), xyz.X(), 1e-6); EXPECT_NEAR(enu.X(), -xyz.Y(), 1e-6); EXPECT_EQ(xyz, sc.LocalFromGlobal(enu)); xyz.Set(1, -1, 0); enu = sc.GlobalFromLocal(xyz); EXPECT_NEAR(enu.Y(), xyz.X(), 1e-6); EXPECT_NEAR(enu.X(), -xyz.Y(), 1e-6); EXPECT_EQ(xyz, sc.LocalFromGlobal(enu)); xyz.Set(2243.52334, 556.35, 435.6553); enu = sc.GlobalFromLocal(xyz); EXPECT_NEAR(enu.Y(), xyz.X(), 1e-6); EXPECT_NEAR(enu.X(), -xyz.Y(), 1e-6); EXPECT_EQ(xyz, sc.LocalFromGlobal(enu)); } // Check SphericalFromLocal { // local frame ignition::math::Vector3d xyz; // spherical coordinates ignition::math::Vector3d sph; // No offset xyz.Set(0, 0, 0); sph = sc.SphericalFromLocal(xyz); // latitude EXPECT_NEAR(sph.X(), lat.Degree(), 1e-6); // longitude EXPECT_NEAR(sph.Y(), lon.Degree(), 1e-6); // elevation EXPECT_NEAR(sph.Z(), elev, 1e-6); // 200 km offset in x (pi/2 heading offset means North). We use // SphericalFromLocal, which means that xyz is a linear movement on // a plane (not along the curvature of Earth). This will result in // a large height offset. xyz.Set(2e5, 0, 0); sph = sc.SphericalFromLocal(xyz); // increase in latitude about 1.8 degrees EXPECT_NEAR(sph.X(), lat.Degree() + 1.8, 0.008); // no change in longitude EXPECT_NEAR(sph.Z(), 3507.024791, 1e-6); ignition::math::Vector3d xyz2 = sc.LocalFromSpherical(sph); EXPECT_EQ(xyz, xyz2); } // Check position projection { // WGS84 coordinate obtained from online mapping software // > gdaltransform -s_srs WGS84 -t_srs EPSG:4978 // > latitude longitude altitude // > X Y Z ignition::math::Vector3d tmp; ignition::math::Vector3d osrf_s(37.3877349, -122.0651166, 32.0); ignition::math::Vector3d osrf_e( -2693701.91434394, -4299942.14687992, 3851691.0393571); ignition::math::Vector3d goog_s(37.4216719, -122.0821853, 30.0); ignition::math::Vector3d goog_e( -2693766.71906146, -4297199.59926038, 3854681.81878812); // Local tangent plane coordinates (ENU = GLOBAL) coordinates of // Google when OSRF is taken as the origin: // > proj +ellps=WGS84 +proj=tmerc // +lat_0=37.3877349 +lon_0=-122.0651166 +k=1 +x_0=0 +y_0=0 // > -122.0821853 37.4216719 (LON,LAT) // > -1510.88 3766.64 (EAST,NORTH) ignition::math::Vector3d vec(-1510.88, 3766.64, -3.29); // Convert degrees to radians osrf_s.X() *= 0.0174532925; osrf_s.Y() *= 0.0174532925; // Set the ORIGIN to be the Open Source Robotics Foundation common::SphericalCoordinates sc2(st, ignition::math::Angle(osrf_s.X()), ignition::math::Angle(osrf_s.Y()), osrf_s.Z(), 0.0); // Check that SPHERICAL -> ECEF works tmp = sc2.PositionTransform(osrf_s, common::SphericalCoordinates::SPHERICAL, common::SphericalCoordinates::ECEF); EXPECT_NEAR(tmp.X(), osrf_e.X(), 8e-2); EXPECT_NEAR(tmp.Y(), osrf_e.Y(), 8e-2); EXPECT_NEAR(tmp.Z(), osrf_e.Z(), 1e-2); // Check that ECEF -> SPHERICAL works tmp = sc2.PositionTransform(tmp, common::SphericalCoordinates::ECEF, common::SphericalCoordinates::SPHERICAL); EXPECT_NEAR(tmp.X(), osrf_s.X(), 1e-2); EXPECT_NEAR(tmp.Y(), osrf_s.Y(), 1e-2); EXPECT_NEAR(tmp.Z(), osrf_s.Z(), 1e-2); // Check that SPHERICAL -> LOCAL works tmp = sc2.LocalFromSpherical(goog_s); EXPECT_NEAR(tmp.X(), vec.X(), 8e-2); EXPECT_NEAR(tmp.Y(), vec.Y(), 8e-2); EXPECT_NEAR(tmp.Z(), vec.Z(), 1e-2); // Check that SPHERICAL -> LOCAL -> SPHERICAL works tmp = sc2.SphericalFromLocal(tmp); EXPECT_NEAR(tmp.X(), goog_s.X(), 8e-2); EXPECT_NEAR(tmp.Y(), goog_s.Y(), 8e-2); EXPECT_NEAR(tmp.Z(), goog_s.Z(), 1e-2); } } } ////////////////////////////////////////////////// // Test distance TEST_F(SphericalCoordinatesTest, Distance) { ignition::math::Angle latA, longA, latB, longB; latA.Degree(46.250944); longA.Degree(-122.249972); latB.Degree(46.124953); longB.Degree(-122.251683); double d = common::SphericalCoordinates::Distance(latA, longA, latB, longB); EXPECT_NEAR(14002, d, 20); } ///////////////////////////////////////////////// int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }