// Copyright 2008, Google Inc. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
//
//  1. Redistributions of source code must retain the above copyright notice, 
//     this list of conditions and the following disclaimer.
//  2. Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//  3. Neither the name of Google Inc. nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// This file contains the unit tests for the mathematical functions.

#include "kml/base/math_util.h"
#include "boost/scoped_ptr.hpp"
#include "gtest/gtest.h"

namespace kmlbase {

TEST(BaseMathTest, TestAzimuthBetweenPoints) {
  // Sanity checks.
  ASSERT_DOUBLE_EQ(0.0, AzimuthBetweenPoints(0,0,1,0));
  ASSERT_DOUBLE_EQ(90.0, AzimuthBetweenPoints(0,0,0,1));
  ASSERT_DOUBLE_EQ(180.0, AzimuthBetweenPoints(0,0,-1,0));
  ASSERT_DOUBLE_EQ(-90.0, AzimuthBetweenPoints(0,0,0,-1));
  ASSERT_NEAR(-180.0, AzimuthBetweenPoints(0,0,-1,-0.0000001), 0.0001);
  // The example from the Aviation Formulary.
  const double kLAX_lat = 33.944066;
  const double kLAX_lng = -118.408294;
  const double kJFK_lat = 40.642480;
  const double kJFK_lng = -73.788071;
  // The known azimuth from LAX to JFK.
  const double kAzimuth = 65.8687;
  double az1 = AzimuthBetweenPoints(kLAX_lat, kLAX_lng, kJFK_lat, kJFK_lng);
  ASSERT_NEAR(kAzimuth, az1, 0.0001);
  // The return flight.
  const double kAzimuthReturn = -86.1617;
  double az2 = AzimuthBetweenPoints(kJFK_lat, kJFK_lng, kLAX_lat, kLAX_lng);
  ASSERT_NEAR(kAzimuthReturn, az2, 0.0001);
}

TEST(BaseMathTest, TestDistanceBetweenPoints) {
  const double kLAX_lat = 33.944066;
  const double kLAX_lng = -118.408294;
  const double kJFK_lat = 40.642480;
  const double kJFK_lng = -73.788071;
  // The known great circle distance in meters between LAX and JFK.
  const double kDistance = 3970683.0;
  double d = DistanceBetweenPoints(kLAX_lat, kLAX_lng, kJFK_lat, kJFK_lng);
  ASSERT_NEAR(kDistance, d, 0.1);
}

TEST(BaseMathTest, TestDistanceBetweenPoints3d) {
  ASSERT_DOUBLE_EQ(0.0, DistanceBetweenPoints3d(0,0,0,0,0,0));
  ASSERT_DOUBLE_EQ(1.0, DistanceBetweenPoints3d(0,0,0,0,0,1));
  ASSERT_DOUBLE_EQ(1000.0, DistanceBetweenPoints3d(0,0,0,0,0,1000));
  // Assert the 2d example works here.
  double expected = 3970683.0;
  ASSERT_NEAR(expected,
              DistanceBetweenPoints3d(33.944066, -118.408294, 0,
                                      40.642480, -73.788071, 0), 0.1);
  // Put JFK 10,000 km in the sky:
  expected = 4094670.171;
  ASSERT_NEAR(expected,
              DistanceBetweenPoints3d(33.944066, -118.408294, 0,
                                      40.642480, -73.788071, 1000000.0), 0.1);
}

TEST(BaseMathTest, TestElevationBetweenPoints) {
  double lat_from = 0;
  double lng_from = 0;
  double alt_from = 0;
  double lat_to = 0.0000000000001;
  double lng_to = 0.0000000000001;
  // This is basically a flat line.
  double alt_to = 0.0;
  ASSERT_NEAR(
    0.0, ElevationBetweenPoints(lat_from, lng_from, alt_from,
                                lat_to, lng_to, alt_to),
    0.001);
  // Near-vertical.
  alt_to = 10000;
  ASSERT_NEAR(
    90.0, ElevationBetweenPoints(lat_from, lng_from, alt_from,
                                 lat_to, lng_to, alt_to),
    0.001);
  lat_to = 0.145;
  alt_to = 609.6;
  ASSERT_NEAR(
    2.1667, ElevationBetweenPoints(lat_from, lng_from, alt_from,
                                   lat_to, lng_to, alt_to),
    0.001);
  lat_from = 37.0;
  lng_from = -121.98;
  alt_from = 600;
  lat_to = 37.0;
  lng_to = -122.0;
  alt_to = 200;
  ASSERT_NEAR(
    -12.7004, ElevationBetweenPoints(lat_from, lng_from, alt_from,
                                     lat_to, lng_to, alt_to),
    0.001);
}

TEST(BaseMathTest, TestLatLngOnRadialFromPoint) {
  // See http://williams.best.vwh.net/avform.htm#Example
  // Coordinates of LAX.
  const double kLAX_lat = 33.944066;
  const double kLAX_lng = -118.408294;
  // A distance and radial.
  const double kDistance = 185200.0;
  const double kRadial = 66.0;
  // The known-accurate coordinates of the point along that radial from LAX.
  const double kRadial_lat = 34.608154;
  const double kRadial_lng = -116.558327;

  Vec3 v = LatLngOnRadialFromPoint(kLAX_lat, kLAX_lng, kDistance, kRadial);
  ASSERT_NEAR(kRadial_lat, v.get_latitude(), 0.000001);
  ASSERT_NEAR(kRadial_lng, v.get_longitude(), 0.000001);
}


// Tests the GroundDistanceFromRangeAndElevation() function.
TEST(BaseMathTest, TestGroundDistanceFromRangeAndElevation) {
  ASSERT_NEAR(100.0, GroundDistanceFromRangeAndElevation(100.0, 0.0),
              0.000001);
  ASSERT_NEAR(99.939083, GroundDistanceFromRangeAndElevation(100.0, 2.0),
              0.000001);
  ASSERT_NEAR(17.364818, GroundDistanceFromRangeAndElevation(100.0, 80.0),
              0.000001);
  ASSERT_NEAR(0.0, GroundDistanceFromRangeAndElevation(100.0, 90.0),
              0.000001);
  ASSERT_NEAR(17.364818, GroundDistanceFromRangeAndElevation(100.0, 100.0),
              0.000001);
}

// Tests the HeightFromRangeAndElevation() function.
TEST(BaseMathTest, TestHeightFromRangeAndElevation) {
  ASSERT_NEAR(0.0, HeightFromRangeAndElevation(100.0, 0.0),
              0.000001);
  ASSERT_NEAR(3.489950, HeightFromRangeAndElevation(100.0, 2.0),
              0.000001);
  ASSERT_NEAR(98.480775, HeightFromRangeAndElevation(100.0, 80.0),
              0.000001);
  ASSERT_NEAR(100.0, HeightFromRangeAndElevation(100.0, 90.0),
              0.000001);
  ASSERT_NEAR(98.480775, HeightFromRangeAndElevation(100.0, 100.0),
              0.000001);
}

// Tese test the conversion functions.
TEST(BaseMathTest, TestDegToRad) {
  ASSERT_DOUBLE_EQ(0.0, DegToRad(0.0));
  ASSERT_DOUBLE_EQ(M_PI, DegToRad(180.0));
  ASSERT_DOUBLE_EQ(M_PI / 2, DegToRad(90.0));
  ASSERT_DOUBLE_EQ(M_PI / -2, DegToRad(-90.0));
}

TEST(BaseMathTest, TestRadToDeg) {
  ASSERT_DOUBLE_EQ(0.0, RadToDeg(0));
  ASSERT_DOUBLE_EQ(360.0, RadToDeg(2 * M_PI));
  ASSERT_DOUBLE_EQ(90.0, RadToDeg(M_PI / 2));
  ASSERT_DOUBLE_EQ(-90.0, RadToDeg(M_PI / -2));
}

TEST(BaseMathTest, TestMetersToRadians) {
  ASSERT_DOUBLE_EQ(0, MetersToRadians(0));
  ASSERT_DOUBLE_EQ(1, MetersToRadians(6366710));
}

TEST(BaseMathTest, TestRadiansToMeters) {
  ASSERT_DOUBLE_EQ(0, RadiansToMeters(0));
  ASSERT_DOUBLE_EQ(6366710, RadiansToMeters(1));
}

}  // end namespace kmlbase
