//============================================================================
//
//  This file is part of GPSTk, the GPS Toolkit.
//
//  The GPSTk is free software; you can redistribute it and/or modify
//  it under the terms of the GNU Lesser General Public License as published
//  by the Free Software Foundation; either version 3.0 of the License, or
//  any later version.
//
//  The GPSTk is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public
//  License along with GPSTk; if not, write to the Free Software Foundation,
//  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA
//  
//  Copyright 2004, The University of Texas at Austin
//  Dagoberto Salazar - gAGE ( http://www.gage.es ). 2007, 2008, 2011
//
//============================================================================

//============================================================================
//
//This software developed by Applied Research Laboratories at the University of
//Texas at Austin, under contract to an agency or agencies within the U.S. 
//Department of Defense. The U.S. Government retains all rights to use,
//duplicate, distribute, disclose, or release this software. 
//
//Pursuant to DoD Directive 523024 
//
// DISTRIBUTION STATEMENT A: This software has been approved for public 
//                           release, distribution is unlimited.
//
//=============================================================================

/**
 * @file XYZ2NEU.hpp
 * This is a class to change the reference base from ECEF XYZ to topocentric
 * North-East-Up (NEU).
 */

#ifndef GPSTK_XYZ2NEU_HPP
#define GPSTK_XYZ2NEU_HPP

#include "GNSSconstants.hpp"                   // DEG_TO_RAD
#include "Matrix.hpp"
#include "Position.hpp"
#include "TypeID.hpp"
#include "ProcessingClass.hpp"


namespace gpstk
{

      /// @ingroup GPSsolutions 
      //@{

      /** This class changes the reference base from an Earth-Centered,
       *  Earth-Fixed (ECEF) system to a North-East-Up (NEU) topocentric
       *  system, centered at the provided reference location.
       *
       * The NEU system is commonly used when comparing the relative accuracy
       * of a given GNSS data processing strategy. Be mindful, however, that
       * NEU is a "left-handed" reference system, whereas geocentric ECEF and
       * topocentric North-East-Down (NED) are "right-handed" systems.
       *
       * A typical way to use this class follows:
       *
       * @code
       *   RinexObsStream rin("ebre0300.02o");
       *
       *      // Reference position of receiver station
       *   Position nominalPos(4833520.2269, 41537.00768, 4147461.489);
       *
       *      // Some more code and definitions here...
       *
       *      // GDS object
       *   gnssRinex gRin;
       *
       *      // Set model defaults. A typical C1-based modeling is used
       *   ModeledPR modelRef( nominalPos,
       *                       ionoStore,
       *                       mopsTM,
       *                       bceStore,
       *                       TypeID::C1,
       *                       true );
       *
       *      // Let's define a new equation definition to adapt
       *      // solver object to base change
       *   TypeIDSet typeSet;
       *   typeSet.insert(TypeID::dLat);
       *   typeSet.insert(TypeID::dLon);
       *   typeSet.insert(TypeID::dH);
       *   typeSet.insert(TypeID::cdt);
       *   gnssEquationDefinition newEq(TypeID::prefitC, typeSet);
       *
       *      // Declare (and tune) a SolverLMS object
       *   SolverLMS solver;
       *   solver.setDefaultEqDefinition(newEq);
       *
       *      // Declare the base-changing object setting the reference position
       *   XYZ2NEU baseChange(nominalPos);
       *
       *   while(rin >> gRin)
       *   {
       *      gRin >> modelRef >> baseChange >> solver;
       *   }
       *
       * @endcode
       *
       * The "XYZ2NEU" object will visit every satellite in the GNSS data
       * structure that is "gRin" and will apply a rotation matrix to
       * coefficients dx, dy and dz of the design matrix, yielding
       * corresponding dLat, dLon and dH for each satellite.
       *
       * Take notice that the design matrix coefficients dx, dy and dz were
       * computed by the "ModeledPR" object, so that step is mandatory.
       *
       * Also, the "XYZ2NEU" class is effective when properly coupled with
       * the "solver" object (be it based on LMS or WMS). In order to get
       * this, you must instruct the "solver" object to get the solution using
       * a geometry/design matrix based on dLat, dLon and dH, instead of the
       * defaults (dx, dy and dz).
       *
       * The later is achieved defining an appropriate "gnssEquationDefinition"
       * object and instructing "solver" to use it as the default equation
       * definition.
       *
       * @sa XYZ2NED.hpp
       */
   class XYZ2NEU : public ProcessingClass
   {
   public:

         /// Default constructor.
      XYZ2NEU()
         : refLat(0.0), refLon(0.0)
      { init(); };


         /** Common constructor taking reference point latitude and longitude
          *
          * @param lat       Latitude of the reference point.
          * @param lon       Longitude of the reference point.
          */
      XYZ2NEU( const double& lat,
               const double& lon )
      { setLatLon(lat, lon); }


         /** Common constructor taking reference point Position object
          *
          * @param refPos    Reference point Position object.
          */
      XYZ2NEU(const Position& refPos);


         /** Method to set the latitude of the reference point, in degrees.
          *
          * @param lat      Latitude of the reference point, in degrees.
          *
          * @warning If parameter 'lat' is outside the +90/-90 degrees range,
          * then latitude will be set to 0 degrees.
          */
      virtual XYZ2NEU& setLat(const double& lat);


         /// Method to get the latitude of the reference point, in degrees.
      virtual double getLat(void) const
      { return (refLat*RAD_TO_DEG); };


         /** Method to set the longitude of the reference point, in degrees.
          *
          * @param lon       Longitude of the reference point, in degrees.
          */
      virtual XYZ2NEU& setLon(const double& lon);


         /// Method to get the longitude of the reference point, in degrees.
      virtual double getLon(void) const
      { return (refLon*RAD_TO_DEG); };


         /** Method to simultaneously set the latitude and longitude of the
          *  reference point, in degrees.
          *
          * @param lat        Latitude of the reference point, in degrees.
          * @param lon        Longitude of the reference point, in degrees.
          *
          * @warning If parameter 'lat' is outside the +90/-90 degrees range,
          * then latitude will be set to 0 degrees.
          */
      virtual XYZ2NEU& setLatLon( const double& lat,
                                  const double& lon );


         /** Returns a reference to a satTypeValueMap object after
          *  converting from a geocentric reference system to a topocentric
          *  reference system.
          *
          * @param gData     Data object holding the data.
          */
      virtual satTypeValueMap& Process(satTypeValueMap& gData)
         throw(ProcessingException);


         /** Returns a reference to a gnssSatTypeValue object after
          *  converting from a geocentric reference system to a topocentric
          *  reference system.
          *
          * @param gData    Data object holding the data.
          */
      virtual gnssSatTypeValue& Process(gnssSatTypeValue& gData)
         throw(ProcessingException)
      { Process(gData.body); return gData; };


         /** Returns a reference to a gnnsRinex object after converting
          *  from a geocentric reference system to a topocentric reference
          *  system.
          *
          * @param gData    Data object holding the data.
          */
      virtual gnssRinex& Process(gnssRinex& gData)
         throw(ProcessingException)
      { Process(gData.body); return gData; };


         /// Returns a string identifying this object.
      virtual std::string getClassName(void) const;


         /// Destructor.
      virtual ~XYZ2NEU() {};


   private:


         /// Latitude of the reference point (topocentric reference),
         /// in radians.
      double refLat;


         /// Longitude of the reference point (topocentric reference),
         /// in radians.
      double refLon;


         /// Rotation matrix.
      Matrix<double> rotationMatrix;


         /// Set (TypeIDSet) containing the types of data to be converted
         /// (dx, dy, dz).
      TypeIDSet inputSet;


         /// Set (TypeIDSet) containing the resulting types of data
         /// (dLat, dLon, dH).
      TypeIDSet outputSet;


         /// This method builds the rotation matrix according to 'refLat'
         /// and 'refLon' values.
      virtual void init();


   }; // End of class 'XYZ2NEU'

      //@}

}  // End of namespace gpstk

#endif   // GPSTK_XYZ2NEU_HPP
