/*
 * (C) Copyright 1996-2016 ECMWF & INPE.
 * 
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0. 
 * In applying this licence, ECMWF does not waive the privileges and immunities 
 * granted to it by virtue of its status as an intergovernmental organisation nor
 * does it submit to any jurisdiction.
 */

#ifdef WIN32
#pragma warning ( disable: 4786 )
#endif

#include "TeProjection.h"
#include "TeException.h"
#include "TeUtils.h"
#include <cstdlib>  //FAMI
#include <cstring>
#include <stdio.h>
#include <iostream> //FAMI

bool tokenizeWKT(char** wkt, vector<string>& tokens);

TeDatum  TeGetDatumFromWKT(const string& wkt);

TeDatum  TeGetDatumFromProj4(const string& proj4);

//=========================================================
//
//  PROJECTION INFORMATION 
//
//  Auxiliary functions which indicate the information
//  associated to a projection
//
//  Used for reading and writing information
//
//==========================================================


//FAMI: added Satellite
// Name						Units  Long  Lat  Par1  Par2  Sca  Eas  Nor 
const char* teProjInfo[]= {
"Albers",					"1",  "1",  "1", "1",  "1",  "0", "1", "1", 
"LatLong",					"1",  "0",  "0", "0",  "0",  "0", "0", "0",   
"LambertConformal",			        "1",  "1",  "1", "1",  "1",  "0", "1", "1", 
"Mercator",					"1",  "1",  "1", "1",  "0",  "0", "1", "1", 
"Miller",					"1",  "1",  "0", "0",  "0",  "0", "1", "1", 
"UTM",						"1",  "1",  "0", "0",  "0",  "1", "1", "1", 
"Polyconic",				        "1",  "1",  "1", "0",  "0",  "0", "1", "1",
"Sinusoidal",				        "1",  "1",  "0", "0",  "0",  "0", "1", "1",
"CylindricalEquidistant",	                "1",  "1",  "0", "1",  "0",  "0", "1", "1",
"PolarStereographic",		                "1",  "1",  "0", "0",  "0",  "0", "1", "1",
"Satellite",		                        "1",  "1",  "1", "0",  "0",  "0", "1", "1",
"NoProjection",				        "1",  "0",  "0", "0",  "0",  "0", "0", "0"		
};


const char** TeGetProjInfo()
{
	return teProjInfo;
}

TeProjInfo
TeProjectionInfo ( const string& projName  )
{

	TeProjInfoMap pjMap;
	TeProjInfo pjInfo;

	int k = 0;

	for ( int i = 0; i < NUM_PROJ; i++ )
	{
		string name = teProjInfo [k++];
			
	    pjInfo.hasUnits  = atoi ( teProjInfo [k++] );
	    pjInfo.hasLon0   = atoi ( teProjInfo [k++] );
	    pjInfo.hasLat0   = atoi ( teProjInfo [k++] );
	    pjInfo.hasStlat1 = atoi ( teProjInfo [k++] );
		pjInfo.hasStlat2 = atoi ( teProjInfo [k++] );
		pjInfo.hasScale  = atoi ( teProjInfo [k++] );
		pjInfo.hasOffx   = atoi ( teProjInfo [k++] );
	    pjInfo.hasOffy   = atoi ( teProjInfo [k++] );

		pjMap [ name ] = pjInfo;
	}


	TeProjInfoMap::iterator it = pjMap.find ( projName );

	if ( it == pjMap.end() )
		throw TeException ( PROJECTION_NOT_AVAILABLE );

return (*it).second;
}

//========================================================
//
// PROJECTION FACTORY
//
// =======================================================

TeProjection*
TeProjectionFactory::make ( const TeProjectionParams& par )
{
	string punits;
	if (par.units.empty())
		punits = "Meters";
	else
		punits = par.units;

	if ( par.name == "UTM" )
		return new TeUtm ( par.datum, par.lon0, par.lat0,
		par.offx, par.offy, punits, par.scale, par.hemisphere );

	if ( par.name == "LambertConformal")
		return new TeLambertConformal ( par.datum, par.lon0, par.lat0,
		par.offx, par.offy, par.stlat1, par.stlat2, punits );
	
	if ( par.name == "Albers" )
		return new TeAlbers ( par.datum, par.lon0, par.lat0,
		par.offx, par.offy, par.stlat1, par.stlat2, punits );

	if ( par.name == "Miller")
		return new TeMiller ( par.datum, par.lon0, par.offx, par.offy, punits );

	if ( par.name == "LatLong" )
	{
		if (par.units.empty())
			punits = "DecimalDegrees";
		else
			punits = par.units;
		return new TeLatLong ( par.datum, punits );
	}

	if ( par.name == "Polyconic" )
		return new TePolyconic ( par.datum, par.lon0, par.lat0, par.offx, par.offy, punits); 

	if ( par.name == "Mercator" )
		return new TeMercator ( par.datum, par.lon0, par.lat0, 
		par.offx, par.offy, par.stlat1, punits );

	if ( par.name == "Sinusoidal")
		return new TeSinusoidal ( par.datum, par.lon0, par.offx, par.offy, punits );

	if ( par.name == "CylindricalEquidistant")
		return new TeCylindricalEquidistant ( par.datum, par.lon0, par.offx, par.offy, par.stlat1, punits);

	if ( par.name == "PolarStereographic")
		return new TePolarStereographic ( par.datum, par.lon0, par.offx, par.offy,punits, par.hemisphere );

//FAMI
	if ( par.name == "Satellite")
		return new TeSatelliteProjection ( par.datum, par.offx, par.offy, par.pri, par.prj, par.pis, par.pjs, par.lat0, par.lon0, par.prs, par.pscn, par.pyaw);

	if ( par.name == "NoProjection")
		return new TeNoProjection(par.datum,punits);	
	return 0;
}

TeProjectionParams 
TeProjection::params () const
{
	TeProjectionParams par;
	
	par.name  = GPname;
	par.datum = GPdatum;
	par.lon0  = GPlon0;
	par.lat0  = GPlat0;
	par.offx  = GPoffx;
	par.offy  = GPoffy;
	par.stlat1 = GPstlat1;
	par.stlat2 = GPstlat2;
	par.units  = GPunits;
	par.scale  = GPscale;
	par.hemisphere =  GPhemisphere;

return par;
}


TeProjection::TeProjection(const TeProjection& rhs)
{
	GPname  = rhs.GPname;
	GPdatum = rhs.GPdatum;
	GPlon0  = rhs.GPlon0;
	GPlat0  = rhs.GPlat0;
	GPoffx  = rhs.GPoffx;
	GPoffy  = rhs.GPoffy;
	GPstlat1 = rhs.GPstlat1;
	GPstlat2 = rhs.GPstlat2;
	GPunits  = rhs.GPunits;
	GPscale  = rhs.GPscale;
	GPhemisphere = rhs.GPhemisphere;
}

TeProjection& 
TeProjection::operator=(const TeProjection& rhs)
{
	if ( this != &rhs )
	{
		GPname  = rhs.GPname;
		GPdatum = rhs.GPdatum;
		GPlon0  = rhs.GPlon0;
		GPlat0  = rhs.GPlat0;
		GPoffx  = rhs.GPoffx;
		GPoffy  = rhs.GPoffy;
		GPstlat1 = rhs.GPstlat1;
		GPstlat2 = rhs.GPstlat2;
		GPunits  = rhs.GPunits;
		GPscale  = rhs.GPscale;
		GPhemisphere = rhs.GPhemisphere;
	}
	return *this;
}



/*******************************************************************
	CHECKS IF A PROJECTION INSTANCE IS EQUAL TO ANOTHER
********************************************************************/

bool
TeProjection::operator== (const TeProjection& proj)
{
	if (GPname == "NoProjection" && proj.GPname == "NoProjection")
		return true;
	if (GPname  != proj.GPname)
		return false;
	if (!(GPdatum==proj.GPdatum))
		return false;
	if (!TeFPEquals(GPlon0, proj.GPlon0, 0.0000000001))
		return false;
	if (!TeFPEquals(GPlat0, proj.GPlat0, 0.0000000001))
		return false;
	if (!TeFPEquals(GPoffx,proj.GPoffx,0.001))
		return false;
	if (!TeFPEquals(GPoffy,proj.GPoffy,0.001))
		return false;
	if (!TeFPEquals(GPstlat1,proj.GPstlat1,0.0000000001))
		return false;
	if (!TeFPEquals(GPstlat2,proj.GPstlat2,0.0000000001))
		return false;
	if (GPhemisphere!=proj.GPhemisphere)
		return false;
	if (GPunits != proj.GPunits)
		return false;
	if (!TeFPEquals(GPscale,proj.GPscale, 0.0000000001))
		return false;

	return true;
}

/*******************************************************************
	PRINTS INFORMATION ABOUT A PROJECTION
********************************************************************/


void
TeProjection::print ( FILE* file_ )
{
	TeProjInfo pjInfo = TeProjectionInfo ( GPname );

	fprintf(file_,"%s\n", "// Projection Information" );
	fprintf ( file_, "%s %s \n", "PROJECTION", GPname.c_str() );
	fprintf ( file_, "%s %s \n", "DATUM", GPdatum.name().c_str() );
	fprintf ( file_, "%s %s \n", "UNITS", GPunits.c_str() );
	
	if ( pjInfo.hasLon0 )
		fprintf ( file_, "%s %17.6f \n", "ORIGIN LONG", GPlon0 );

	if ( pjInfo.hasLat0 )
		fprintf ( file_, "%s %17.6f \n", "ORIGIN LAT", GPlat0 );

	if ( pjInfo.hasOffx )
		fprintf ( file_, "%s %17.6f \n", "FALSE EASTING", GPoffx );

	if ( pjInfo.hasOffy )
		fprintf ( file_, "%s %17.6f \n", "FALSE NORTHING", GPoffy );
	
	if ( pjInfo.hasStlat1 )
		fprintf ( file_, "%s %17.6f \n", "FIRST STANDARD PARALEL", GPstlat1 );
	
	if ( pjInfo.hasStlat2 )
		fprintf ( file_, "%s %17.6f \n", "SECOND STANDARD PARALEL", GPstlat2 );

	if ( pjInfo.hasScale )
		fprintf ( file_, "%s %17.6f \n", "SCALE", GPscale );

	fprintf(file_,"%s\n", "// End of Projection Information" );
}


string
TeProjection::describe ()
{
	string desc;
	if (GPname == "NoProjection")
	{
		desc = "NoProjection";
		return desc;
	}
	desc = GPname;
	TeProjInfo pjInfo = TeProjectionInfo ( GPname );

	desc =  GPunits;
	if ( pjInfo.hasLon0 )
		desc += "," + Te2String(GPlon0*TeCRD,6);

	if ( pjInfo.hasLat0 )
		desc += "," + Te2String(GPlat0*TeCRD,6);

	if ( pjInfo.hasStlat1 )
		desc += ", " + Te2String(GPstlat1*TeCRD,6);
	
	if ( pjInfo.hasStlat2 )
		desc += "," + Te2String(GPstlat2*TeCRD,6);

	if ( pjInfo.hasOffx )
		desc += "," + Te2String(GPoffx,6);

	if ( pjInfo.hasOffy )
		desc += "," + Te2String(GPoffy,6);
	
	if ( pjInfo.hasScale )
		desc += "," + Te2String(GPscale,6);

	return desc;
}


bool decodifyDescription(const string& projDescription, TeProjectionParams& pars)
{
	vector<string> projDesc;
	if (TeSplitString(projDescription, ",", projDesc) <= 0)
		return false;
	unsigned int npar = projDesc.size();
	TeProjInfo pjInfo = TeProjectionInfo (projDesc[0]);
	pars.name = projDesc[0];
	if (npar > 1)
		pars.units = projDesc[1];
	else 
		return true;

	unsigned int nextp = 2;
	if (pjInfo.hasLon0)
	{
		 if (nextp < npar)
		 {
			pars.lon0 = atof(projDesc[nextp].c_str()) * TeCDR;
			nextp++;
		 }
		 else
			 return false;
	}

	if (pjInfo.hasLat0)
	{
		 if (nextp < npar)
		 {
			pars.lat0 = atof(projDesc[nextp].c_str()) * TeCDR;
			nextp++;
		 }
		 else
			 return false;
	}

	if (pjInfo.hasStlat1)
	{
		 if (nextp < npar)
		 {
			pars.stlat1 = atof(projDesc[nextp].c_str()) * TeCDR;
			nextp++;
		 }
		 else
			 return false;
	}

	if (pjInfo.hasStlat2)
	{
		 if (nextp < npar)
		 {
			pars.stlat2 = atof(projDesc[nextp].c_str()) * TeCDR;
			nextp++;
		 }
		 else
			 return false;
	}

	if (pjInfo.hasOffx)
	{
		 if (nextp < npar)
		 {
			pars.offx = atof(projDesc[nextp].c_str());
			nextp++;
		 }
		 else
			 return false;
	}

	if (pjInfo.hasOffy)
	{
		 if (nextp < npar)
		 {
			pars.offy = atof(projDesc[nextp].c_str());
			nextp++;
		 }
		 else
			 return false;
	}
	
	if (pjInfo.hasScale)
	{
		 if (nextp < npar)
		 {
			pars.scale = atof(projDesc[nextp].c_str());
			nextp++;
		 }
		 else
			 return false;
	}
	return true;
}

/********************************************************************
		Planimetric datum transformation 
********************************************************************/
void
TeProjection :: ChangeLL (double &lon1, double &lat1)		
{
	double 	equad1,	// Squared eccentricity - datum 1
			equad2,	// Squared eccentricity - datum 2
			n1,		// Great normal of ellipsoid - datum 1
			n2,		// Great normal od ellipsoid - datum 2
			x1,		// Geocentric cartesian coordinates - datum 1 
			y1,
			z1,
			x2,		// Geocentric cartesian coordinates - datum 2
			y2,
			z2,
			d,lat2,		// Ancillary variable
			lon2;
	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

 	if( GPdestination->GPdatum.radius() == 0.) return;

	int	flt1 = (int)(Pflt*1000000000.);
	int	flt2 = (int)(GPdestination->GPdatum.flattening()*1000000000.);
	if (Prd == GPdestination->GPdatum.radius() && flt1==flt2)
		return;

// Geocentric cartesian coordinates calculation - datum 1
	equad1 = 2.*Pflt;
	equad1 -= Pflt*Pflt;
	double a1,a2,a3;
	a1 = sin(lat1);
	a1 *= a1;
	a1 *= equad1;
	a2 = 1-a1;
	n1 = Prd/sqrt(a2);
	x1 = n1*cos(lat1)*cos(lon1);
	y1 = n1*cos(lat1)*sin(lon1);
	z1 = (n1*(1-equad1))*sin(lat1);

// Geocentric cartesian coordinates calculation - datum 2
	if (GPdatum.xShift() == TeMAXFLOAT || GPdestination->GPdatum.xShift() == TeMAXFLOAT)
	{
		x2 = x1; 
		y2 = y1;
		z2 = z1;
	}
	else
	{	
		x2 = x1 + (GPdatum.xShift() - GPdestination->GPdatum.xShift());
		y2 = y1 + (GPdatum.yShift() - GPdestination->GPdatum.yShift());
		z2 = z1 + (GPdatum.zShift() - GPdestination->GPdatum.zShift());
	}

// Geodetic coordinates calculation - datum 2
	equad2 = 2.*GPdestination->GPdatum.flattening();
	equad2 -= (GPdestination->GPdatum.flattening())*(GPdestination->GPdatum.flattening());
	lat2 = lat1;
	do
	{
		a1 = sin(lat2);
		a1 *= a1;
		a1 *= equad2;
		a2 = 1-a1;
		n2 = GPdestination->GPdatum.radius()/sqrt(a2);
		a1 = equad2*sin(lat2);
		a1 *= n2;
		a1 += z2;
		a2 = x2*x2;
		a2 += (y2*y2);
		a3 = sqrt(a2);
		lat2 = atan2(a1,a3);
		a1 = sin(lat2);
		a1 *= a1;
		a1 *= equad2;
		a2 = 1-a1;
		a3 = sqrt(a2);
		d = (GPdestination->GPdatum.radius()/a3)-n2;
	}
	while (fabs(d) > 0.0000001);
	lon2 = atan2(y2,x2);
	lat1 = lat2;
	lon1 = lon2;
}

/********************************************************************
		GEODETIC TO UTM COORDINATES
********************************************************************/


TeCoord2D
TeUtm :: LL2PC (TeCoord2D& ptll)
{

	double	k0,		// Scale factor
		equad,		// Squared eccentricity
		n,		// Great normal of ellipsoid
		elinquad,	// ancillary variables 
		aux1,aux2,aux3,aux4,aux5,aux6,aux7,aux8,aux9,
		aux10,aux11,aux12,aux13,t,c,ag,m,ptllx,ptlly;

	double Pflt = GPdatum.flattening();
	ptllx = ptll.x();
	ptlly = ptll.y();
	k0 = 1. - (1./2500.);
	equad = 2.*Pflt;	
	equad -= Pflt*Pflt;
	elinquad = equad/(1. - equad);
	aux1 = equad*equad;
	aux2 = aux1*equad;
	aux3 = sin((double)2*ptlly);
	aux4 = sin((double)4*ptlly);
	aux5 = sin((double)6*ptlly);

	double a1, a2, a3;
	a1 = equad/4.;
	a2 = 3.*aux1/64.;
	a3 = 5.*aux2/256.;
	aux6 = (1-a1-a2-a3)*ptlly;

	a1 = 3.*equad/8.;
	a2 = 3.*aux1/32.;
	a3 = 45.*aux2/1024.;
	aux7 = (a1+a2+a3)*aux3;

	a1 = 15.*aux1/256.;
	a2 = 45.*aux2/1024.;
	aux8 = (a1+a2)*aux4;

	aux9 = 35.*aux2;
	aux9 /= 3072.;
	aux9 *= aux5;

	a1 = sin(ptlly);	
	a1 *= a1;		
	a1 *= equad;		
	n = GPdatum.radius()/sqrt((double)1-a1);


	t = tan(ptlly);		
	t *= t;			

	c = cos(ptlly);		
	c *= c;			
	c *= elinquad;		
	ag = (ptllx-GPlon0)*cos(ptlly);
	m = GPdatum.radius()*(aux6 - aux7 + aux8 - aux9);


	a1 = ag*ag*ag;
	aux10 = (1.-t+c)*a1/6.;

	a1 = 5.-(18.*t);
	a2 = a1+t*t;
	a1 = a2+72.*c;
	a2 = a1-(58.*elinquad);
	a1 = ag*ag*ag;		
	a1 *= ag;		
	a1 *= ag;		
	aux11 = a2*a1/120.;	

	a1 = 5.-t+9.*c;
	a2 = 4.*c*c;
	a3 = ag*ag;
	a3 *= ag;
	a3 *= ag;
	aux12 = (a1+a2)*a3/24.;

	a1 = 61.-(58.*t) ;
	a2 = a1+(t*t);
	a1 = a2+(600.*c);
	a2 = a1-(330.*elinquad);
	a1 = ag*ag*ag;		
	a1 *= ag;		
	a1 *= ag;		
	a1 *= ag;		
	aux13 = a2*a1/720.;	

	ptllx = k0*n*(ag + aux10 + aux11);

	a1 = ag*ag/2;
	a2 = a1+aux12+aux13;
	a3 = n*tan(ptlly);
	a1 = a2*a3;
	ptlly = k0*(m+a1);

	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));
}


/********************************************************************
		UTM TO GEODETIC COORDINATES
********************************************************************/

TeCoord2D
TeUtm :: PC2LL (TeCoord2D& ptpc)
{
	double  k0,             	// Scale factor
		equad,			// Squared eccentricity
		n1,			// Great normal of ellipsoid
		elinquad,		// Ancillary variables
		e1,aux1,aux2,aux3,aux4,aux5,m,mi,aux6,aux7,aux8,lat1,
		c1,t1, quoc,r1,d,aux9,aux10,aux11,aux12,ptpcx,ptpcy;

	double Prd  = GPdatum.radius();
	double Pflt = GPdatum.flattening();
	
	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;		

	k0 = 1. - (1./2500.);
	equad = 2.*Pflt;		
	equad -= Pflt*Pflt;		
	elinquad = equad/(1. - equad);

	double a1,a2,a3;
	a1 = sqrt((double)1-equad);
	e1 = (1.-a1)/(1.+a1);

	aux1 = equad*equad;
	aux2 = aux1*equad;
	aux3 = e1*e1;
	aux4 = e1*aux3;
	aux5 = aux4*e1;

	m = ptpcy/k0;


	a1 = 1.-(equad/4.);
	a2 = 3.*(aux1/64.);
	a3 = 5.*(aux2/256.);
	mi = m/(Prd*(a1-a2-a3));


	a1 = 3.*(e1/2.);
	a2 = 27.*(aux4/32.);
	a3 = sin((double)2*mi);
	aux6 = (a1-a2)*a3;

	a1 = 21.*(aux3/16.);
	a2 = 55.*(aux5/32.);
	a3 = sin((double)4*mi);
	aux7 = (a1-a2)*a3;

	a1 = 151.*(aux4/96.);
	a2 = sin((double)6*mi);
	aux8 = a1*a2;

	lat1 = mi + aux6 + aux7 + aux8;

	c1 = cos(lat1);			
	c1 *= c1;			
	c1 *= elinquad;			

	t1 = tan(lat1);			
	t1 *= t1;			

	a1 = sin(lat1);			
	a1 *= a1;			
	a1 *= equad;			
	a2 = 1.-a1;
	n1 = Prd/sqrt(a2);

	a1 = sin(lat1);
	a1 *= a1;
	a1 *= equad;
	a2 = 1.-a1;
	quoc = a2*a2*a2;

	r1 = Prd*(1.-equad);
	r1 /= sqrt(quoc);

	d = ptpcx;
	d /= (n1*k0);


	a1 = 5.+(3.*t1);
	a1 += (10.*c1);
	a1 += (-4.*(c1*c1));
	a1 += (-9.*elinquad);
	a1 *= d;
	a1 *= d;
	a1 *= d;
	a1 *= d;
	aux9 = a1/24.;

	a1 = 61.+(90.*t1);
	a1 += (298.*c1);
	a1 += (45.*(t1*t1));
	a1 += (-252.*elinquad);
	a1 += (-3.*c1*c1);
	aux10 = d*d*d;			
	aux10 *= d;			
	aux10 *= d;			
	aux10 *= d;			
	aux10 *= a1;
	aux10 /= 720.;

	a1 = d*d*d;
	a1 /= 6.;
	a2 = 2.*t1;
	a2 += (1.+c1);
	aux11 = d-a2*a1;

	a1 = 5.-(2.*c1);
	a1 += (28.*t1);
	a1 += (-3.*c1*c1);
	a1 += (8.*elinquad);
	a1 += (24.*t1*t1);
	aux12 = d*d*d;			
	aux12 *= d;			
	aux12 *= d;			
	aux12 *= a1;			
	aux12 /= 120.;			

	a1 = d*d/2.;
	a1 += (- aux9 + aux10);
	a2 = tan(lat1)/r1;
	a2 *= n1;
	ptpcy = lat1-a2*a1;

	ptpcx= GPlon0 + (aux11 + aux12)/cos(lat1);

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return(TeCoord2D(ptpcx,ptpcy));
}

/********************************************************************
		GEODETIC TO MERCATOR COORDINATES
********************************************************************/
TeCoord2D
TeMercator :: LL2PC (TeCoord2D& ptll)
{
	double	equad,			//Squared eccentricity
		aux1,			// Ancillary variables
		aux2,aux3,aux4,aux5,aux6,ptllx,ptlly;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptllx = ptll.x();		
	ptlly = ptll.y();		

	equad = 2.*Pflt;		
	equad -= Pflt*Pflt;		
	double	a1,a2,a3;		
 
	a1 = tan(ptlly/(double)2);	
	a2 = 1.+a1;			
	a3 = 1.-a1;			
	aux1 = a2/a3;			
 
	a1 = equad*equad/4.;		
	a1 += equad;			
	a2 = equad*equad*equad/8.;	
	a3 = sin(ptlly);		
	aux2 = (a1+a2)*a3;		
 
	a1 = equad*equad/12.;		
	a2 = equad*equad*equad/16.;	
	a3 = sin((double)3*ptlly);	
	aux3 = (a1+a2)*a3;		
 
	a1 = equad*equad*equad/80.;	
	a2 = sin((double)5*ptlly);	
	aux4 = a1*a2;			
	aux5 = cos(GPstlat1);
 
	a1 = sin(GPstlat1);		
	a1 *= a1;			
	a1 *= equad;			
	a2 = sqrt((double)1-a1);	
	aux6 = 1./a2;			

	ptllx = Prd*(ptllx - GPlon0)*aux5*aux6;
	ptlly = Prd*(log(aux1) - aux2 + aux3 - aux4)*aux5*aux6;

	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}

/********************************************************************
		MERCATOR TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeMercator :: PC2LL (TeCoord2D& ptpc)
{
	double	equad,			//Squared eccentricity 
	        pi,			// PII value
		t,			// Ancillary variables
		xx,aux1,aux2,aux3,aux4,aux5,ptpcx,ptpcy;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	equad = 2.*Pflt;		
	equad -= Pflt*Pflt;		
	pi = 4.*atan((double)1);
	double	a1,a2;		
	aux1 = cos(GPstlat1);

	a1 = sin(GPstlat1);		
	a1 *= a1;			
	a1 *= equad;			
	a2 = sqrt((double)1-a1);	
	aux2 = 1./a2;			
	ptpcx = ptpcx/(aux1*aux2);
	ptpcy = ptpcy/(aux1*aux2);
	t = exp(-ptpcy/Prd);
	xx = pi/2. - 2.*atan(t);
 
	a1 = equad/2.;			
	a1 += 5.*equad*equad/24.;	
	a1 += equad*equad*equad/12.;	
	a2 = sin((double)4*atan(t));	
	aux3 = a1*a2;			
 
	a1 = 7.*equad*equad/48.;	
	a1 += 29.*equad*equad*equad/240.;	
	a2 = sin((double)8*atan(t));	
	aux4 = -a1*a2;			
 
	a1 = 7.*equad*equad*equad/120.;	
	a2 = sin((double)12*atan(t));	
	aux5 = a1*a2;		 

	ptpcy = xx + aux3 + aux4 + aux5;
	ptpcx = ptpcx/Prd + GPlon0;
	
	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return(TeCoord2D(ptpcx,ptpcy));
}




/********************************************************************
		GEODETIC TO LAMBERT CONIC COORDINATES
********************************************************************/
TeCoord2D
TeLambertConformal :: LL2PC (TeCoord2D& ptll)
{

	double	equad,			// Squared eccentricity
		e,			// Ancillary variables
		m1,m2,aux1,aux2,aux0,t1,t2,t0,n,efe,ro0,aux,
		t,ro,teta,ptllx,ptlly;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptllx = (double)ptll.x();
	ptlly = (double)ptll.y();

	equad = 2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);

	m1 = cos(GPstlat1)/sqrt((double)1-equad*pow(sin(GPstlat1),(double)2));
	m2 = cos(GPstlat2)/sqrt((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux1 = sqrt(((double)1-e*sin(GPstlat1))/((double)1+e*sin(GPstlat1)));
	aux2 = sqrt(((double)1-e*sin(GPstlat2))/((double)1+e*sin(GPstlat2)));
	aux0 = sqrt(((double)1-e*sin(GPlat0))/((double)1+e*sin(GPlat0)));
	t1 = ((1.-tan(GPstlat1/(double)2))/(1.+tan(GPstlat1/(double)2)))/pow(aux1,e);
	t2 = ((1.-tan(GPstlat2/(double)2))/(1.+tan(GPstlat2/(double)2)))/pow(aux2,e);
	t0 = ((1.-tan(GPlat0/(double)2))/(1.+tan(GPlat0/(double)2)))/pow(aux0,e);

	if (GPstlat1 == GPstlat2)
		n = sin(GPstlat1);
	else
		n = (log(m1)-log(m2))/(log(t1)-log(t2));

	efe = m1/(n*pow(t1,n));
	ro0 = Prd*efe*pow(t0,n);

	aux = sqrt(((double)1-e*sin(ptlly))/((double)1+e*sin(ptlly)));
	t = ((1.-tan(ptlly/(double)2))/(1.+tan(ptlly/(double)2)))/pow(aux,e);
	ro = Prd*efe*pow(t,n);
	teta = n*(ptllx - GPlon0);
	ptllx = ro*sin(teta);
	ptlly = ro0 - ro*cos(teta);
	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}



/********************************************************************
		LAMBERT CONIC TO GEODETIC COORDINATES
 ********************************************************************/
TeCoord2D
TeLambertConformal :: PC2LL (TeCoord2D& ptpc)
{
	double	equad,			// Squared eccentricity 
		pi,			// PI value
		e,m1,m2,aux1,aux2,aux0,t1,t2,t0,n,efe,ro0,ro,teta,
		t,xx,aux3,aux4,aux5,ptpcx,ptpcy;

	int	sinal;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();
	

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	equad = 2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);
	pi = 4.*atan((double)1);

	m1 = cos(GPstlat1)/sqrt((double)1-equad*pow(sin(GPstlat1),(double)2));
	m2 = cos(GPstlat2)/sqrt((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux1 = sqrt(((double)1-e*sin(GPstlat1))/((double)1+e*sin(GPstlat1)));
	aux2 = sqrt(((double)1-e*sin(GPstlat2))/((double)1+e*sin(GPstlat2)));
	aux0 = sqrt(((double)1-e*sin(GPlat0))/((double)1+e*sin(GPlat0)));
	t1 = ((1.-tan(GPstlat1/(double)2))/(1.+tan(GPstlat1/(double)2)))/pow(aux1,e);
	t2 = ((1.-tan(GPstlat2/(double)2))/(1.+tan(GPstlat2/(double)2)))/pow(aux2,e);
	t0 = ((1.-tan(GPlat0/(double)2))/(1.+tan(GPlat0/(double)2)))/pow(aux0,e);

	if (GPstlat1 == GPstlat2)
		n = sin(GPstlat1);
	else
		n = (log(m1)-log(m2))/(log(t1)-log(t2));

	efe = m1/(n*pow(t1,n));
	ro0 = Prd*efe*pow(t0,n);

	sinal = (int)(n/fabs(n));
	ro = sqrt(ptpcx*ptpcx + (ro0-ptpcy)*(ro0-ptpcy));
	ro *= sinal;
	teta = atan(ptpcx/(ro0-ptpcy));
	t = pow((ro/(Prd*efe)),(double)1/n);
	xx = pi/2. - 2.*atan(t);
	aux3 = equad/2. + 5.*equad*equad/24. + equad*equad*equad/12.;
	aux4 = 7.*equad*equad/48. + 29.*equad*equad*equad/240.;
	aux5 = (7.*equad*equad*equad/120.)*sin(12.*atan(t));

	ptpcy = xx + aux3*sin(4.*atan(t)) - aux4*sin(8.*atan(t)) + aux5;
	ptpcx = teta/n + GPlon0;
	
	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return (TeCoord2D(ptpcx,ptpcy));
}

/********************************************************************
		GEODETIC TO POLYCONIC COORDINATES
********************************************************************/
TeCoord2D
TePolyconic :: LL2PC (TeCoord2D& ptll)
{
	double	equad,			// Squared eccentricity 
		n,			// Great normal of ellipsoid
		aux01,			// Ancillary variables
		aux02,aux03,aux04,m0,aux1,aux2,aux3,
		aux4,m,e,ptllx,ptlly;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptllx = (double)ptll.x();
	ptlly = (double)ptll.y();

	equad = 2.*Pflt - pow(Pflt,(double)2);

	aux01 = (1.-equad/4.-3.*equad*equad/64.-5.*equad*equad*equad/256.)*GPlat0;
	aux02 = (3.*equad/8+3.*equad*equad/32.+45.*equad*equad*equad/1024.)*sin((double)2*GPlat0);
	aux03 = (15.*equad*equad/256.+45.*equad*equad*equad/1024.)*sin((double)4*GPlat0);
	aux04 = (35.*equad*equad*equad/3072.)*sin((double)6*GPlat0);
	m0 = Prd*(aux01 - aux02 + aux03 - aux04);

	if (ptlly == 0.)
	{
		ptllx = Prd*(ptllx - GPlon0);
		ptlly = -m0;
	}
	else
	{
		aux1 = (1.-equad/4.-3.*equad*equad/64.-5.*equad*equad*equad/256.)*ptlly;
		aux2 = (3.*equad/8+3.*equad*equad/32.+45.*equad*equad*equad/1024.)*sin((double)2*ptlly);
		aux3 = (15.*equad*equad/256.+45.*equad*equad*equad/1024.)*sin((double)4*ptlly);
		aux4 = (35.*equad*equad*equad/3072.)*sin((double)6*ptlly);
		m = Prd*(aux1 - aux2 + aux3 - aux4);
		n = Prd/sqrt((double)1-equad*pow(sin(ptlly),(double)2));
		e = (ptllx - GPlon0)*sin(ptlly);
		ptllx = n*sin(e)/tan(ptlly);
		ptlly = m - m0 + (n*(1. - cos(e))/tan(ptlly));
	}
	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}

/*******************************************************************
		POLYCONIC TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TePolyconic :: PC2LL (TeCoord2D& ptpc)
{	
	double	A,	
		B,
		C,
		equad,			// Squared eccentricity
		aux01,			// Ancillary variables
		aux02,aux03,aux04,m0,mn,mnl,ma,cp,lat1,lat2,aux05,
		aux06,aux07,aux08,aux09,aux10,aux11,aux12,aux21,
		aux22,aux23,aux24,ptpcx,ptpcy;


	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	equad = 2.*Pflt - pow(Pflt,(double)2);

	// Initialize latitude with latitude of origin
	aux01 = (1.-equad/4.-3.*equad*equad/64.-5.*equad*equad*equad/256.)*GPlat0;
	aux02 = (3.*equad/8+3.*equad*equad/32.+45.*equad*equad*equad/1024.)*sin((double)2*GPlat0);
	aux03 = (15.*equad*equad/256.+45.*equad*equad*equad/1024.)*sin((double)4*GPlat0);
	aux04 = (35.*equad*equad*equad/3072.)*sin((double)6*GPlat0);
	m0 = Prd*(aux01 - aux02 + aux03 - aux04);

	if (ptpcy == (-m0))
	{
		ptpcy= 0.;
		ptpcx = ptpcx/Prd + GPlon0;
	}
	else
	{
		A = (m0 + ptpcy)/Prd;
		B = ((ptpcx*ptpcx)/(Prd*Prd)) +(A*A);

		lat2 = A;

		do
		{
			C = (sqrt(1.- equad*sin(lat2)*sin(lat2)))*tan(lat2);

			// mn calculation	
			aux21 = (1.-equad/4.-3.*equad*equad/64.-5.*equad*equad*equad/256.)*lat2;
			aux22 = (3.*equad/8.+3.*equad*equad/32.+45.*equad*equad*equad/1024.)*sin((double)2*lat2);
			aux23 = (15.*equad*equad/256.+45.*equad*equad*equad/1024.)*sin((double)4*lat2);
			aux24 = (35.*equad*equad*equad/3072.)*sin((double)6*lat2);
			mn = Prd*(aux21 - aux22 + aux23 - aux24);
		
			// mnl calculation
			aux05 = 1.- equad/4.-3.*equad*equad/64.-5.*equad*equad*equad/256.;
			aux06 = 2.*(3.*equad/8.+3.*equad*equad/32.+45.*equad*equad*equad/1024.)*cos((double)2*lat2);
			aux07 = 4.*(15.*equad*equad/256.+45.*equad*equad*equad/1024.)*cos((double)4*lat2);
			aux08 = 6.*(35.*equad*equad*equad/3072.)*cos((double)6*lat2);
			mnl = aux05 - aux06 + aux07- aux08;

			// ma calculation
			ma = mn/Prd;

			aux09 = (A*(C*ma+1)-ma)-(0.5*(ma*ma+B)*C);
			aux10 = equad*sin((double)2*lat2)*(ma*ma+B-2.*A*ma);
			aux11 = 4.*C+(A-ma);
			aux12 = C*mnl-(2./sin((double)2*lat2));

			// New latitude calculation 
			lat1 = lat2 - (aux09/((aux10/(aux11*aux12)) - mnl));
			cp = fabs(lat1-lat2) ;
			lat2 = lat1;


		}while(cp > 0.0000000001);

		ptpcy = lat1;
		ptpcx = ((asin((ptpcx*C)/Prd))/(sin(lat1))) + GPlon0;
	}

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return (TeCoord2D(ptpcx,ptpcy));	

}

/********************************************************************
	GEODETIC TO LAT LONG
********************************************************************/
TeCoord2D
TeLatLong :: LL2PC (TeCoord2D& ptll)
{
	return TeCoord2D( ptll.x()*TeCRD, ptll.y()*TeCRD ); 
}

//FAMI
void
TeLatLong :: LL2PC (double xi, double yi, double& xo, double& yo) const
{
	xo = xi*TeCRD;
	yo = yi*TeCRD;
	return;
}

/********************************************************************
	LAT LONG TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeLatLong :: PC2LL (TeCoord2D& ptpc)
{
	double ptpcx, ptpcy;

	ptpcx = ptpc.x()*TeCDR;
	ptpcy = ptpc.y()*TeCDR;
	
	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return TeCoord2D(ptpcx,ptpcy); 
}

//FAMI
void
TeLatLong :: PC2LL (double xi, double yi, double& ptpcx, double& ptpcy)
{
	ptpcx = xi*TeCDR;
	ptpcy = yi*TeCDR;
	
	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return; 
}

/********************************************************************
	GEODETIC TO ALBERS EQUAL-AREA COORDINATES
********************************************************************/
TeCoord2D
TeAlbers :: LL2PC (TeCoord2D& ptll)
{

	double	equad,			// Squared eccentricity
		e,			// Ancillary variables
		m1,m2,aux1,aux10,aux11,aux12,aux2,aux20,aux21,
		aux22,q0,q1,q2,q,n,c,ro0,teta,ro,ptllx,ptlly;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptllx = (double)ptll.x();
	ptlly = (double)ptll.y();

	equad = 2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);

	m1 = cos(GPstlat1)/sqrt((double)1-equad*pow(sin(GPstlat1),(double)2));
	m2 = cos(GPstlat2)/sqrt((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux1 = sin(ptlly)/((double)1-equad*pow(sin(ptlly),(double)2));
	aux10 = sin(GPlat0)/((double)1-equad*pow(sin(GPlat0),(double)2));
	aux11 = sin(GPstlat1)/((double)1-equad*pow(sin(GPstlat1),(double)2));
	aux12 = sin(GPstlat2)/((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux2 = log((1. - e*sin(ptlly))/(1. + e*sin(ptlly)));
	aux20 = log((1. - e*sin(GPlat0))/(1. + e*sin(GPlat0)));
	aux21 = log((1. - e*sin(GPstlat1))/(1. + e*sin(GPstlat1)));
	aux22 = log((1. - e*sin(GPstlat2))/(1. + e*sin(GPstlat2)));
	q0 = (1. - equad)*(aux10 - (1./(2.*e))*aux20);
	q1 = (1. - equad)*(aux11 - (1./(2.*e))*aux21);
	q2 = (1. - equad)*(aux12 - (1./(2.*e))*aux22);
	q = (1. - equad)*(aux1 - (1./(2.*e))*aux2);
	n = (m1*m1 - m2*m2)/(q2 - q1);
	c = m1*m1 + n*q1;
	ro0 = Prd*sqrt(c - n*q0)/n;
	teta = n*(ptllx - GPlon0);
	ro = Prd*sqrt(c - n*q)/n;

	ptllx = ro*sin(teta);
	ptlly = ro0 - ro*cos(teta);

	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}


/********************************************************************
		ALBERS EQUAL-AREA TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeAlbers :: PC2LL(TeCoord2D& ptpc)
{

	double	equad,			// Squared eccentricity
		e,			// Eccentricity
		m1,			// Ancillary variable
		m2,aux10,aux11,aux12,aux20,aux21,aux22,q0,q1,q2,
		n,c,ro0,ro,q,aux,beta,aux1,aux2,aux3,teta,ptpcx,ptpcy;

	int	sinal;			// Ancillary variable	

	
	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	sinal = (int)(GPstlat2/fabs(GPstlat2));
	equad = 2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);

	m1 = cos(GPstlat1)/sqrt((double)1-equad*pow(sin(GPstlat1),(double)2));
	m2 = cos(GPstlat2)/sqrt((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux10 = sin(GPlat0)/((double)1-equad*pow(sin(GPlat0),(double)2));
	aux11 = sin(GPstlat1)/((double)1-equad*pow(sin(GPstlat1),(double)2));
	aux12 = sin(GPstlat2)/((double)1-equad*pow(sin(GPstlat2),(double)2));
	aux20 = log((1. - e*sin(GPlat0))/(1. + e*sin(GPlat0)));
	aux21 = log((1. - e*sin(GPstlat1))/(1. + e*sin(GPstlat1)));
	aux22 = log((1. - e*sin(GPstlat2))/(1. + e*sin(GPstlat2)));
	q0 = (1. - equad)*(aux10 - (1./(2.*e))*aux20);
	q1 = (1. - equad)*(aux11 - (1./(2.*e))*aux21);
	q2 = (1. - equad)*(aux12 - (1./(2.*e))*aux22);
	n = (m1*m1 - m2*m2)/(q2 - q1);
	c = m1*m1 + n*q1;
	ro0 = Prd*sqrt(c - n*q0)/n;
	ro = sqrt(ptpcx*ptpcx + (ro0 - ptpcy)*(ro0 - ptpcy));
	q = (c - (ro*ro*n*n/(Prd*Prd)))/n;
	aux = ((1. - equad)/(2.*e))*log((1. - e)/(1. + e));
	beta = asin(q/(1. - aux));
	aux1 = (equad/3. + 31.*equad*equad/180.+517.*equad*equad*equad/5040.)*sin(2.*beta);
	aux2 = (23.*equad*equad/360.+251.*equad*equad*equad/3780.)*sin(4.*beta);
	aux3 = (761.*equad*equad*equad/45360.)*sin(6.*beta);
	teta = fabs(atan(ptpcx/(ro0 - ptpcy)));

	if (sinal == 1)
	{
		if (ptpcx < 0.)
			teta = -teta;
	}

	if (sinal == -1)
	{
		if (ptpcx > 0.)
			teta *= sinal;
	}

	ptpcy = beta + aux1 + aux2 + aux3;
	ptpcx = GPlon0 + (teta/n);

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return (TeCoord2D(ptpcx,ptpcy));
}

/*******************************************************************
		GEODETIC TO MILLER COORDINATES
********************************************************************/
TeCoord2D
TeMiller :: LL2PC (TeCoord2D& ptll)
{

	double	pi,		// PI value
		ptllx,ptlly;	// Ancillary variables 

	int	sinal;		// Ancillary variables

	double Prd  = GPdatum.radius();
	
	ptllx = (double)ptll.x();
	ptlly = (double)ptll.y();

	pi = 4.*atan((double)1);
	sinal = (int)(fabs(ptlly)/ptlly);
	
	ptllx = Prd*(ptllx - GPlon0);

	if (sinal == 1)
		ptlly = (Prd/0.8)*(log(tan(pi/4. + 0.4*ptlly)));
	else
	{
		ptlly *= sinal;
		ptlly = (Prd/0.8)*(log(tan(pi/4. + 0.4*ptlly)));
		ptlly *= sinal;
	}

	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}


/********************************************************************
		MILLER TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeMiller :: PC2LL (TeCoord2D& ptpc)
{

	double	e,		// Neperian logarithm base 
		pi,		// PI value
		ptpcx,ptpcy;	// Ancillary variables

	int	sinal;		// Ancillary variable

	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	e = 2.718281828;
	pi = 4.*atan((double)1);
	sinal = (int)(fabs(ptpcy)/ptpcy);

	ptpcx = GPlon0 + (ptpcx/Prd);

	if (sinal == 1)
		ptpcy = 2.5*atan(pow(e,(0.8*ptpcy/Prd)))-5.*pi/8.;
	else
	{
		ptpcy *= sinal;
		ptpcy = 2.5*atan(pow(e,(0.8*ptpcy/Prd))) - 5.*pi/8.;
		ptpcy *= sinal;
	}

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return(TeCoord2D(ptpcx,ptpcy));
}


/*******************************************************************
		GEODETIC TO SINUSOIDAL COORDINATES
********************************************************************/
TeCoord2D
TeSinusoidal :: LL2PC (TeCoord2D& ptll)
{

	double	ptllx,ptlly;	// Ancillary variables 

	double Prd  = GPdatum.radius();
	
	ptllx = (double)ptll.x();
	ptlly = (double)ptll.y();
	
	ptllx = Prd*(ptllx - GPlon0);
	ptllx *= cos(ptlly);

	ptlly = Prd*ptlly;
	
	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));	
}


/********************************************************************
		SINUSOIDAL TO GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeSinusoidal :: PC2LL (TeCoord2D& ptpc)
{

	double	ptpcx,ptpcy;	// Ancillary variables

	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	ptpcy = ptpcy/Prd;

	ptpcx = GPlon0 + (ptpcx/(Prd*cos(ptpcy)));
	
	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return(TeCoord2D(ptpcx,ptpcy));
}

/*******************************************************************
		GEODETIC TO CYLINDRICAL EQUIDISTANT COORDINATES
********************************************************************/
TeCoord2D
TeCylindricalEquidistant :: LL2PC (TeCoord2D& ptll)
{
	double	ptllx,ptlly;

	double Prd  = GPdatum.radius();

	ptllx = ptll.x();
	ptlly = ptll.y();

	ptllx = Prd*(ptllx - GPlon0)*cos(GPstlat1);
	ptlly = Prd*ptlly;

	return(TeCoord2D(ptllx+GPoffx,ptlly+GPoffy));
}


/*******************************************************************
	CYLINDRICAL EQUIDISTANT TO	GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TeCylindricalEquidistant :: PC2LL (TeCoord2D& ptpc)
{
	double	ptpcx,ptpcy;

	double Prd  = GPdatum.radius();

	ptpcx = ptpc.x()-GPoffx;
	ptpcy = ptpc.y()-GPoffy;

	ptpcy = ptpcy/Prd;
	ptpcx = GPlon0 + ptpcx/(Prd*cos(GPstlat1));

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return (TeCoord2D(ptpcx,ptpcy));
}

/*******************************************************************
		GEODETIC TO POLAR STEREOGRAPHIC COORDINATES
********************************************************************/
TeCoord2D
TePolarStereographic :: LL2PC (TeCoord2D& ptll)
{
	double	k0,				// Scale factor
		e,					// Eccentricity
		pi,					// Auxillary variables
		lon0,				// auxilliary origin longitude
		t,ro,aux1,aux2,aux3,ptllx,ptlly;
	
	int GPhemis;
	if (GPhemisphere == TeNORTH_HEM)
		GPhemis = 1;
	else
		GPhemis = -1;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();
	
	ptllx = ptll.x();
	ptlly = ptll.y();

	k0 = 0.933;	// Standard parallel 60 degrees 
	e = sqrt((double)2*Pflt - pow(Pflt,(double)2));
	pi = 4.*atan((double)1);

	ptlly *= GPhemis;
	ptllx *= GPhemis;
	lon0 = (GPhemis == 1) ? GPlon0 : -GPlon0;

	aux1 = (1. - e*sin(ptlly))/(1. + e*sin(ptlly));
	t = tan((pi/4.) - (ptlly/2.))/pow(aux1,(e/(double)2));
	aux2 = pow(((double)1 + e),((double)1 + e));
	aux3 = pow(((double)1 - e),((double)1 - e));
	ro = 2.*Prd*k0*t/sqrt(aux2*aux3);

	aux1   = ro*sin(ptllx - lon0);
	ptlly = -ro*cos(ptllx - lon0);
	aux1 *= GPhemis;
	ptlly *= GPhemis;
	
	if (GPhemis == -1)
	{
		aux1 *= GPhemis;
		ptlly *= GPhemis;
	}
//	return(Point((float)aux1,(float)ptlly));
	return (TeCoord2D(aux1+GPoffx,ptlly+GPoffy));

}

//FAMI
void
TePolarStereographic :: LL2PC (double xi, double yi, double& xo, double& yo) const
{
	double	k0,				// Scale factor
		e,					// Eccentricity
		pi,					// Auxillary variables
		lon0,				// auxilliary origin longitude
		t,ro,aux1,aux2,aux3,ptllx,ptlly;
	
	int GPhemis;
	if (GPhemisphere == TeNORTH_HEM)
		GPhemis = 1;
	else
		GPhemis = -1;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();
	
	ptllx = xi;
	ptlly = yi;

	k0 = 0.933;	// Standard parallel 60 degrees 
	e = sqrt((double)2*Pflt - pow(Pflt,(double)2));
	pi = 4.*atan((double)1);

	ptlly *= GPhemis;
	ptllx *= GPhemis;
	lon0 = (GPhemis == 1) ? GPlon0 : -GPlon0;

	aux1 = (1. - e*sin(ptlly))/(1. + e*sin(ptlly));
	t = tan((pi/4.) - (ptlly/2.))/pow(aux1,(e/(double)2));
	aux2 = pow(((double)1 + e),((double)1 + e));
	aux3 = pow(((double)1 - e),((double)1 - e));
	ro = 2.*Prd*k0*t/sqrt(aux2*aux3);

	aux1   = ro*sin(ptllx - lon0);
	ptlly = -ro*cos(ptllx - lon0);
	aux1 *= GPhemis;
	ptlly *= GPhemis;
	
	if (GPhemis == -1)
	{
		aux1 *= GPhemis;
		ptlly *= GPhemis;
	}

	xo = aux1+GPoffx;
	yo = ptlly+GPoffy;
	return;
}

//FAMI
void TePolarStereographic :: LL2PC (ostream& java) const 
{
	double	k0,			// Scale factor
		equad,			// Squared eccentricity
		e,		        // Eccentricity
		pi,			// Ancillary variables
		lon0,			// auxilliary origin longitude
		aux1,aux2,aux3,aux4,aux5, aux6, aux7;
	
		
	//java << "\tpx = xpc - " << GPoffx << ";\n";
	//java << "\tpy = ypc - " << GPoffy << ";\n";

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();
	k0   = 0.933;
	
	pi = 4.*atan((double)1); 
	equad =  2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);
	
	
	if (GPhemisphere != TeNORTH_HEM) {
		//java << "\tpx *= -1;\n";
		//java << "\tpy *= -1;\n";
	}
	lon0 = (GPhemisphere == TeNORTH_HEM) ? GPlon0 : -GPlon0;
	string name = (GPhemisphere == TeNORTH_HEM) ? "polar_north" : "polar_south";
	//java << "\tro = Math.sqrt(px*px + py*py);\n";	
	aux1 = pow(((double)1 + e),((double)1 + e));
    aux6 = pow(((double)1 + e),((double)1 + e));
    aux7 = pow(((double)1 - e),((double)1 - e));
	aux2 = 2.*Prd*k0;
	
	//java << "\tt = (ro* " << sqrt(aux1*aux2) << ")/(" << 2.*Prd*k0 << ") " << ";\n";
	//java << "\txx = " << pi/2. << " - 2.*Math.atan(t);\n";
	
	aux3 =  equad/2. + 5.*equad*equad/24. + equad*equad*equad/12.;
	aux4 = 7.*equad*equad/48. + 29.*equad*equad*equad/240.;
	aux5 = 7.*equad*equad*equad/120.;
	
	
		java <<     "\"name\" : \"" <<  name << "\"," ;
		java <<     "\"GPoffx\" : " <<  GPoffx << ",";
		java <<     "\"GPoffy\" : " <<  GPoffy << ",";
		java <<     "\"lon0\" : " << GPlon0 << "," ;
        java <<     "\"e\" : " << e << "," ;
		java <<     "\"coeff1\" : " << aux1 << "," ;
		java <<     "\"coeff2\" : " << aux2 << "," ;
		java <<     "\"coeff3\" : " << aux3 << "," ;
		java <<     "\"coeff4\" : " << aux4 << "," ;
		java <<     "\"coeff5\" : " << aux5 <<  "," ;
		java <<     "\"coeff6\" : " << aux6 <<  "," ;
		java <<     "\"coeff7\" : " << aux7 <<  "," ;
        
		
		

	//java << "\tyll = xx + " << aux3 << "*Math.sin(2*xx) + " << aux4 << "*Math.sin(4*xx) + "<< aux5 << "*Math.sin(6*xx);\n";

	//java << "\tif (py != 0.) \n";
	//java << "\t\txll = " << lon0 << "+ Math.atan(px/(-py));\n";

	if (GPhemisphere == TeNORTH_HEM)
	{
		//java << "\tif (and(greater(px, 0.), greater(py,0.)))\n";
		//java << "\t\txll = xll + " << pi << ";\n";
		//java << "\telse if (and(smaller(px,0.), greater(py,0.)))\n";
		//java << "\t\txll = xll - " << pi << ";\n";
		//java << "\telse if (and(greater(px, 0.), equal(py,0.)))\n";
		//java << "\t\txll = " << lon0  << "+ " << pi << "/ 2.;\n";
		//java << "\telse if (and(smaller(px, 0.),  equal(py,0.)))\n";
		//java << "\t\txll = " << lon0 << " - " << pi << "/ 2.;\n";
		//java << "\telse if (and(equal(px,0.), equal(py, 0.)))\n";
		//java << "\t\txll = " << lon0 << ";\n";
	}
	else
	{
		//java << "\tyll = -yll;\n";
		//java << "\txll = -xll;\n";

		//java << "\tif (and(greater(px,0.), smaller(py,0)))\n";
		//java << "\t\txll = xll + " << pi << ";\n";
		//java << "\telse if (and (smaller(px, 0.),  smaller(py,0)))\n";
		//java << "\t\txll = xll - " << pi << ";\n";
		//java << "\telse if (and(greater(px,0.), equal(py,0.)))\n";
		//java << "\t\txll = " << lon0 << " + " << pi << "/ 2.;\n";
		//java << "\telse if (and(smaller(px,0.), equal(py, 0.)))\n";
		//java << "\t\txll = " << lon0 << " - " << pi << " / 2.;\n";
		//java << "\telse if (and(equal(px,0.), equal(py, 0.)))\n";
		//java << "\t\txll = " << lon0 << ";\n";
	}

	//java << "\tif (smaller(xll, (-" << pi << ") )) xll += 2.* "<< pi << ";\n";
	//java << "\telse if (greater(xll, " << pi << ")  )    xll -= 2.*" << pi << ";\n";
	
	//java << "\txll = xll * " << 180/3.14159 << ";\n";
	//java << "\tyll = yll * " << 180/3.14159 << ";\n\n";

	//java << "\treturn { lon :  xll, lat : yll } ;\n";
	//java << "}\n";
	
}
/*******************************************************************
	POLAR STEREOGRAPHIC TO	GEODETIC COORDINATES
********************************************************************/
TeCoord2D
TePolarStereographic :: PC2LL (TeCoord2D& ptpc)
{
	double	k0,			// Scale factor

		equad,			// Squared eccentricity
		e,		        // Eccentricity
		pi,			// Ancillary variables
		lon0,			// auxilliary origin longitude
		ro,t,xx,aux1,aux2,aux3,aux4,aux5,ptpcx=0.,ptpcy,px,py;

	px = (double)ptpc.x()-GPoffx;
	py = (double)ptpc.y()-GPoffy;

	int GPhemis;
	if (GPhemisphere == TeNORTH_HEM)
		GPhemis = 1;
	else
		GPhemis = -1;

	double Pflt = GPdatum.flattening();
	double Prd  = GPdatum.radius();

//	k0 = 0.994;	// Standard parallel 80.1 degrees 
	k0 = 0.933;	// Standard parallel 60 degrees
	pi = 4.*atan((double)1);
	equad = 2.*Pflt - pow(Pflt,(double)2);
	e = sqrt(equad);

	if (GPhemis == -1)
	{
		px *= GPhemis;
		py *= GPhemis;
	}

	lon0 = (GPhemis == 1) ? GPlon0 : -GPlon0;

	ro = sqrt(px*px + py*py);
	aux1 = pow(((double)1 + e),((double)1 + e));
	aux2 = pow(((double)1 - e),((double)1 - e));
	t = (ro*sqrt(aux1*aux2))/(2.*Prd*k0);
	xx = pi/2. - 2.*atan(t);
	aux3 = equad/2. + 5.*equad*equad/24. + equad*equad*equad/12.;
	aux4 = 7.*equad*equad/48. + 29.*equad*equad*equad/240.;
	aux5 = 7.*equad*equad*equad/120.;

	ptpcy = xx + aux3*sin((double)2*xx) + aux4*sin((double)4*xx) + aux5*sin((double)6*xx);

	if (py != 0.)
		ptpcx = lon0 + atan(px/(-py));


	if (GPhemis == 1)
	{
		if (px > 0. && py > 0.)
			ptpcx = ptpcx + pi;
		else if (px < 0. && py > 0.)
			ptpcx = ptpcx - pi;
		else if (px > 0. && py == 0.)
			ptpcx = lon0 + pi / 2.;
		else if (px < 0. && py == 0.)
			ptpcx = lon0 - pi / 2.;
		else if (px == 0. && py == 0.)
			ptpcx = lon0;
	}
	else
	{
		ptpcy *= GPhemis;
		ptpcx *= GPhemis;

		if (px > 0. && py < 0.)
			ptpcx = ptpcx + pi;
		else if (px < 0. && py < 0.)
			ptpcx = ptpcx - pi;
		else if (px > 0. && py == 0.)
			ptpcx = lon0 + pi / 2.;
		else if (px < 0. && py == 0.)
			ptpcx = lon0 - pi / 2.;
		else if (px == 0. && py == 0.)
			ptpcx = lon0;
	}

	if (ptpcx < (-pi)) ptpcx += 2.*pi;
	else if (ptpcx > pi)    ptpcx -= 2.*pi;

	if( GPdestination && !(datum() == GPdestination->datum()))
		ChangeLL(ptpcx,ptpcy);

	return (TeCoord2D(ptpcx,ptpcy));
}

//FAMI
/************************************************************************
		GEODETIC COORDINATES TO SATELLITE PROJECTION
*************************************************************************/
TeCoord2D 
TeSatelliteProjection::LL2PC(TeCoord2D& ptll)
{
	double equad, n, x, y, z, sn, dp, dl,xlo,yla,lin,col,resx,resy;
	double x1, x2, a, b, c,  Rd2, Rm, Rm2, Rs2, v;

//	xlo = ptpc.x()-GPoffx;
//	yla = ptpc.y()-GPoffy;
	xlo = ptll.x();
	yla = ptll.y();

	// Cartesian geocentric coordinates
	xlo = xlo - GPlon0;
	yla = yla - GPlat0;
	equad = 2.*GPdatum.flattening() - pow(GPdatum.flattening(),(double)2);
	n = GPdatum.radius()/sqrt((double)1-equad*pow(sin(yla),(double)2));
	x = n*cos(yla)*cos(xlo);
	y = n*cos(yla)*sin(xlo);
	z = (n*(1-equad))*sin(yla);
 
	// Field of view angles
	dp = atan(y / (SPrs - x));
	dl = atan(z * cos(dp) /(SPrs - x));

	// Visibility test
	if ( x < 0.0 )
	{
		col = TeMAXFLOAT;
		lin = TeMAXFLOAT;
		return (TeCoord2D(col,lin));
	}
	else
	{
		Rd2 = GPdatum.radius() * GPdatum.radius() ;
		Rm  = GPdatum.radius() * (1 - GPdatum.flattening());
		Rm2 = Rm * Rm ;
		Rs2 = SPrs * SPrs;

		v = (tan(dp)*tan(dp)*cos(dp)*cos(dp)*Rm2)+(tan(dl)*tan(dl)*Rd2);
		a = cos(dp)*cos(dp)*Rm2 + v;      
		b = -2.*SPrs*v;
		c = Rs2*v-Rd2*Rm2*cos(dp)*cos(dp);

		// Equation Resolution a*x**2+b*x+c=0 
		v = (b*b) - (4.*a*c);
		if (v < 0.0) v=0.0;
   		x1 = (-b + sqrt(v)) / (2.*a);
		x2 = (-b - sqrt(v)) / (2.*a);
		if (x1 < x2)  x1 = x2;

		if ( fabs(x-x1) > 1.0 )
		{
			col = TeMAXFLOAT;
			lin = TeMAXFLOAT;
			return (TeCoord2D(col,lin));
		}
	}
	// Line and column of image
	if (SPscn == 0.) sn = 1.0;  else sn = -1.0;
	col = (sn * dp / SPrj + SPjs );
	lin = (-sn * dl / SPri + SPis);

	// Axis rotation correction due to yaw angle
	col = col*cos(SPyaw) - lin*sin(SPyaw);
	lin = lin*cos(SPyaw) + col*sin(SPyaw);
	resx = tan(SPrj) * (SPrs - GPdatum.radius());
	resy = tan(SPri) * (SPrs - GPdatum.radius());

	return (TeCoord2D(col*resx,-(lin*resy)));
}

/************************************************************************
		SATELLITE PROJECTION TO GEODETIC COORDINATES
************************************************************************/
TeCoord2D
TeSatelliteProjection::PC2LL(TeCoord2D& ptpc)
{
	double	dl, dp, x, y, z, x1, x2, equad, n, d, sn,
		a, b, c,  Rd2, Rm,Rm2, Rs2,v,ptpcx,ptpcy,
		yla,xlo,resx,resy;

	resx = tan(SPrj) * (SPrs - GPdatum.radius());
	resy = tan(SPri) * (SPrs - GPdatum.radius());
	ptpcx = ptpc.x()/resx;
	ptpcy = -ptpc.y()/resy;

	// Axis rotation correction due yaw angle
	ptpcx = ptpcx*cos(SPyaw) + ptpcy*sin(SPyaw);
	ptpcy = ptpcy*cos(SPyaw) - ptpcx*sin(SPyaw);

	// Field of view angles
	if (SPscn == 0.) sn =  1.0;
	else 		 sn = -1.0;
	dl = - sn * ((ptpcy - SPis) * SPri);
	dp =   sn * ((ptpcx - SPjs) * SPrj);

	// Cartesian coordinates
	Rd2 = GPdatum.radius() * GPdatum.radius() ;
	Rm  = GPdatum.radius() * (1 - GPdatum.flattening());
	Rm2 = Rm * Rm ;
	Rs2 = SPrs * SPrs;

	v = (tan(dp)*tan(dp)*cos(dp)*cos(dp)*Rm2)+(tan(dl)*tan(dl)*Rd2);
	a = cos(dp)*cos(dp)*Rm2 + v;      
	b = -2.*SPrs*v;
	c = Rs2*v-Rd2*Rm2*cos(dp)*cos(dp);

	// Equation Resolution a*x**2+b*x+c=0 
	v = (b*b) - (4.*a*c);
	if (v < 0)
	{
		xlo = TeMAXFLOAT;
		yla = TeMAXFLOAT;
		return (TeCoord2D(xlo,yla));
	}

   	x1 = (-b + sqrt(v)) / (2.*a);
	x2 = (-b - sqrt(v)) / (2.*a);

	if ( x1 >= x2)  x = x1;
	else		  x = x2;

	z = (SPrs - x) * tan(dl) / cos(dp);
	y = (SPrs - x) * tan(dp);

	// Geodetic coordinates
	equad 	= 2. * GPdatum.flattening() - GPdatum.flattening() * GPdatum.flattening();

	double aux = SPrs * dl / GPdatum.radius();
	if (aux > 1.0) aux = 1.0;
	if (aux < -1.0) aux = -1.0;

	yla = asin(aux);
	
	do
	{
		n= GPdatum.radius()/sqrt((double)1-equad*pow(sin(yla),(double)2));
		yla = atan((z + n*equad*sin(yla))/sqrt(x*x + y*y));
		d = GPdatum.radius()/sqrt((double)1-equad*pow(sin(yla),(double)2)) - n;
	}
	while (fabs(d) > 0.001);

	xlo = atan(y/x) + GPlon0;
	yla = yla + GPlat0;

	return (TeCoord2D(xlo,yla));
}

TeProjectionParams 
TeSatelliteProjection::params () const
{
	TeProjectionParams par;
	par = TeProjection::params();

	par.pri  = SPri;
	par.prj  = SPrj;
	par.pis  = SPis;
	par.pjs  = SPjs;
	par.prs  = SPrs;
	par.pscn = SPscn;
	par.pyaw = SPyaw;

return par;
}

TeDatum 
TeGetDatumFromProj4(const string& proj4)
{
	unsigned int pos;
    string attribute;
    string value;
	double dx, dy, dz;
	dx = dy = dz = 0.0;

	TeDatum dat;
	dat.name("UserDefined");
	for(unsigned int i = 0; i < proj4.size(); i++)
    {
        for( pos = i; i != proj4.size() && proj4[i] != '=';i++);

        attribute = proj4.substr(pos+1, i-pos-1);

        for(pos = i; i != proj4.size() && proj4[i] != ' ';i++);

        if(proj4[i] == ' ') value = proj4.substr(pos+1, i-pos-1);
        else                value = proj4.substr(pos+1, i-pos);
		
		double b;
		// Datum parameters
        if(attribute == "R")	// radius for a spherical ellipsoid
		{
			dat = TeDatum("Spherical",atof(value.c_str()));
			return dat;
		}
		else if(attribute == "a")   // Earth equatorial radius
			dat.radius(atof(value.c_str()));
		else if (attribute == "b")
			b = atof(value.c_str());
	    else if(attribute == "f")	// Earth flattening
			dat.flattening(atof(value.c_str()));
	    else if(attribute == "rf")	// Earth  reverse flattening (f = 1/rf)
			dat.flattening(1/atof(value.c_str()));
		else if (attribute == "es") // eccentricity squared (f=1-(1-es)^0.5) 
			dat.flattening(1-sqrt(1-atof(value.c_str())));
		else if (attribute == "e")	// eccentricity (f=1-(1-es^2)^0.5)
			dat.flattening(1-sqrt(1-pow(atof(value.c_str()),2)));
		else if (attribute == "ellps")
			dat.name(value);
		else if (attribute == "towgs84")
		{
			vector<string> deltas;
			TeSplitString(value, ",", deltas);
			
			if (deltas.size()>=3)
			{
				dx = atof(deltas[0].c_str());
				dy = atof(deltas[1].c_str());
				dz = atof(deltas[2].c_str());
			}
		}
	}
	if ( dx==0.0 && dy== 0 && dz == 0.0)
	{
		dat = TeDatum("WGS84",6378137,0.003352811,66.87,-4.37,38.52);
	}
	// this is a SAD69
	else if (dx == -66.87 && dy == 4.37 &&  dz == -38.52)
	{
		dat = TeDatum("SAD69",6378160,0.003352892,0.0,0.0,0.0);
	}
	else
	{
		dat.dx_ = dx;
		dat.dy_ = dy;
		dat.dz_ = dz;
	}
	return dat;
}

TeProjection* TeGetTeProjectionFromSProj(const string& proj4)
{
	// a map from sproj4 projections to TerraLib projections
	map<string,string> sprojToTeProj;
	sprojToTeProj["aea"] = "Albers";
	sprojToTeProj["latlong"] = "LatLong";
	sprojToTeProj["lcc"] = "LambertConformal";
	sprojToTeProj["merc"] = "Mercator";
	sprojToTeProj["mill"] = "Miller";
	sprojToTeProj["utm"] = "UTM";
	sprojToTeProj["sinu"] = "Sinusoidal";
	sprojToTeProj["poly"] = "Polyconic";
	sprojToTeProj["eqc"] = "CylindricalEquidistant";
	sprojToTeProj["ups"] = "PolarStereographic";
	sprojToTeProj["sat"] = "Satellite"; //FAMI

    unsigned int pos;
    string attribute;
    string value;

  	TeProjectionParams par;
	par.hemisphere = TeNORTH_HEM;
	par.offx = 500000;
	par.offy = 0;
	par.units = "";
    for(unsigned int i = 0; i < proj4.size(); i++)
    {
        for( pos = i; i != proj4.size() && proj4[i] != '=';i++);

        attribute = proj4.substr(pos+1, i-pos-1);

        for(pos = i; i != proj4.size() && proj4[i] != ' ';i++);

        if(proj4[i] == ' ') value = proj4.substr(pos+1, i-pos-1);
        else                value = proj4.substr(pos+1, i-pos);

        if(attribute == "proj")
		{
			map<string,string>::iterator it = sprojToTeProj.find(value);
			if (it != sprojToTeProj.end())
				par.name = sprojToTeProj[value];
			else
				par.name = "NoProjection"; 
		}
	// Currently this routine understands the following parameters from
	// a sproj description: lon_0, lat_0, lat_1, lat_2, k, zone, x_0, y_0

        else if(attribute == "lon_0") 
			par.lon0 = atof(value.c_str())*TeCDR;
        else if(attribute == "lat_0") 
			par.lat0 = atof(value.c_str())*TeCDR;
        else if(attribute == "lat_1") 
			par.stlat1 = atof(value.c_str())*TeCDR;
        else if(attribute == "lat_2") 
			par.stlat2 = atof(value.c_str())*TeCDR;
        else if(attribute == "k") 
			par.scale = atof(value.c_str());
        else if(attribute == "zone") 
			par.lon0 = (-183 + 6*(atoi(value.c_str()))*TeCDR);
		else if (attribute == "south") 
		{
			par.hemisphere = TeSOUTH_HEM;
			par.offy = 10000000;
		}
		else if(attribute == "x_0") 
			par.offx = atof(value.c_str());
 		else if(attribute == "y_0") 
			par.offy = atof(value.c_str());
		else if (attribute == "units")
			par.units = value;
// TODO: sproj4 also allowd the selection of standard, predefined
// ellipsoid figures using the option +ellps=acronym. But we are not decoding 
// them yet because of our problem with the shiftings from SAD69
    }
	par.datum = TeGetDatumFromProj4(proj4);
	return TeProjectionFactory::make(par);
}

string TeGetSProjFromTeProjection(TeProjection* teproj)
{
	map<string,string> teProjToSProj;
	teProjToSProj["Albers"] = "aea";
	teProjToSProj["LatLong"] = "latlong";
	teProjToSProj["LambertConformal"] = "lcc";
	teProjToSProj["Mercator"] = "merc";
	teProjToSProj["Miller"] = "mill";
	teProjToSProj["UTM"] = "utm";
	teProjToSProj["Sinusoidal"] = "sinu";
	teProjToSProj["Polyconic"] = "poly";
	teProjToSProj["CylindricalEquidistant"] = "eqc";
	teProjToSProj["PolarStereographic"] = "ups";
	teProjToSProj["Satellite"] = "sat"; //FAMI

    string sresp = "+proj=";
	map<string,string>::iterator it = teProjToSProj.find(teproj->name());
	if (it == teProjToSProj.end())
	{
		sresp += "noprojection";
		return sresp;
	}
	else
		sresp += it->second;

	TeProjInfo pjInfo = TeProjectionInfo(teproj->name());
	if ( pjInfo.hasLon0 )
		sresp += " +lon_0=" + Te2String(teproj->lon0()*TeCRD,6);

	if ( pjInfo.hasLat0 )
		sresp += " +lat_0=" + Te2String(teproj->lat0()*TeCRD,6);

	if ( pjInfo.hasStlat1 )
		sresp += " +lat_1=" + Te2String(teproj->stLat1()*TeCRD,6);
	
	if ( pjInfo.hasStlat2 )
		sresp += " +lat_2=" +Te2String(teproj->stLat2()*TeCRD,6);

	if ( pjInfo.hasOffx )
		sresp += " +x_0=" + Te2String(teproj->offX(),6);

	if ( pjInfo.hasOffy )
		sresp += " +y_0=" + Te2String(teproj->offY(),6);
	
	if ( pjInfo.hasScale )
		sresp += " +k="  + Te2String(teproj->scale(),6);

	string datumD = teproj->datum().getProj4Description();
	if (!datumD.empty())
		sresp += datumD;
	return sresp;
}

string 
TeGetWKTFromTeProjection(TeProjection* teproj)
{
	string wkt = "";
	if (!teproj)
		return wkt;

	string wktDatum = teproj->datum().getWKTDescription();
	if (teproj->name() == "LatLong")
		return wktDatum;

	map<string,string> teProjToWKT;
	teProjToWKT["Albers"] = "Albers_Conic_Equal_Area";
	teProjToWKT["LambertConformal"] = "Lambert_Conformal_Conic_1SP";
	teProjToWKT["Mercator"] = "Mercator_1SP";
	teProjToWKT["Miller"] = "Miller_Cylindrical";
	teProjToWKT["UTM"] = "Transverse_Mercator";
	teProjToWKT["Sinusoidal"] = "Sinusoidal";
	teProjToWKT["Polyconic"] = "Polyconic";
	teProjToWKT["CylindricalEquidistant"] = "Equirectangular";
	teProjToWKT["PolarStereographic"] = "Polar_Stereographic";
	teProjToWKT["Satellite"] = "Satellite"; //FAMI
	teProjToWKT["NoProjection"] = "Unknown";

	wkt = "PROJCS[\"" + teproj->name() + "\",";
	wkt += wktDatum;
	wkt += ",PROJECTION[\"" + teProjToWKT[teproj->name()] + "\"]";

	TeProjInfo pjInfo = TeProjectionInfo(teproj->name());
	if (pjInfo.hasLon0)
		wkt +=",PARAMETER[\"central_meridian\","+ Te2String(teproj->lon0()*TeCRD,6)+"]";

	if (pjInfo.hasLat0)
		wkt +=",PARAMETER[\"latitude_of_origin\","+ Te2String(teproj->lat0()*TeCRD,6)+"]";

	if (pjInfo.hasStlat1)
		wkt +=",PARAMETER[\"standard_parallel_1\","+ Te2String(teproj->stLat1()*TeCRD,6)+"]";
	
	if (pjInfo.hasStlat2)
		wkt +=",PARAMETER[\"standard_parallel_2\","+ Te2String(teproj->stLat2()*TeCRD,6)+"]";

	if (pjInfo.hasOffx)
		wkt +=",PARAMETER[\"false_easting\","+ Te2String(teproj->offX(),6)+"]";

	if (pjInfo.hasOffy)
		wkt +=",PARAMETER[\"false_northing\","+ Te2String(teproj->offY(),6)+"]";

	if (pjInfo.hasScale)
		wkt +=",PARAMETER[\"scale_factor\","+ Te2String(teproj->scale(),6)+"]";
	
	wkt += ",UNIT[\"meter\",1]]";
	return wkt;
}

bool tokenizeWKT(char** wkt, vector<string>& tokens)
{
	const char *pszInput =  *wkt;
	int isInQuote = false;
	
	string token = "";
	int  sizeToken = 0;
	while (*pszInput != '\0')
	{
		if (*pszInput == '"')
		{
			isInQuote = !isInQuote;
		}
		else if (!isInQuote && 
				(*pszInput == '[' || *pszInput == ']' || *pszInput == ',' || 
					*pszInput == '(' || *pszInput == ')' ))
		{
			break;
		}
		else if (!isInQuote && 
				(*pszInput == ' ' || *pszInput == '\t' || 
				*pszInput == 10 || *pszInput == 13))
		{
		/* just skip over whitespace */
		} 
		else
		{
			token += *pszInput;
			++sizeToken;
		}
		pszInput++;
	}
	if (*pszInput == '\0')
		return true;
		
	tokens.push_back(token);
	
	bool result;  // read sublist
	if( *pszInput == '[' || *pszInput == '(' )
	{
		do
		{
			pszInput++; // Skip bracket or comma.
			result = tokenizeWKT((char **) &pszInput, tokens);
			if (!result )
				return result;          
		}while (*pszInput == ',');
	
		if (*pszInput != ')' && *pszInput != ']')
			return result;
		pszInput++;
	}
	*wkt = (char *) pszInput;
	return true;
}



TeProjection* 
TeGetTeProjectionFromWKT(const string& wkt)
{
	map<string,string> wktToTeProj;
	wktToTeProj["Albers_Conic_Equal_Area"] = "Albers";
	wktToTeProj["Lambert_Conformal_Conic_1SP"] = "LambertConformal";
	wktToTeProj["Mercator_1SP"] = "Mercator";
	wktToTeProj["Miller_Cylindrical"] = "Miller";
	wktToTeProj["Transverse_Mercator"] = "UTM";
	wktToTeProj["Sinusoidal"] = "Sinusoidal";
	wktToTeProj["Polyconic"] = "Polyconic";
	wktToTeProj["Equirectangular"] = "CylindricalEquidistant";
	wktToTeProj["Polar_Stereographic"] = "PolarStereographic";
	wktToTeProj["Satellite"] = "Satellite"; //FAMI

	TeDatum dat = TeDatumFactory::makeFromWKT(wkt);
	vector<string> tokens;
	char* wktchar = new char[wkt.size()];
	strcpy(wktchar,wkt.c_str());
	bool res = tokenizeWKT((char **) &wktchar, tokens);
	delete [] wktchar;
	if (!res)
		return 0;

	// geographic coordinates 

	TeProjectionParams par;
	unsigned int i = 1;
	bool isProjected = false;
	if (tokens[0] == "GEOGCS")
	{
		isProjected = false;
		par.name = "LatLong";
	}
	else if (tokens[0] == "PROJCS")
	{
		isProjected = true;
	}
	else
		return 0;
	par.datum = dat;
	if (!isProjected)
		return TeProjectionFactory::make(par);

	while (i<tokens.size() && tokens[i] != "PROJECTION")
		++i;
	if (i== tokens.size())
		return 0;
	map<string,string>::iterator it = wktToTeProj.find(tokens[++i]);
	if (it != wktToTeProj.end())
		par.name = it->second;
	else
		return 0;
	while (tokens[++i] == "PARAMETER")
	{
		++i;
		if (TeStringCompare(tokens[i],"latitude_of_origin",false))
			par.lat0 = atof(tokens[++i].c_str())*TeCDR;
		else if (TeStringCompare(tokens[i],"central_meridian",false))
			par.lon0 = atof(tokens[++i].c_str())*TeCDR;
 		else if (TeStringCompare(tokens[i],"standard_parallel_1",false))
			par.stlat1 = atof(tokens[++i].c_str())*TeCDR;
 		else if (TeStringCompare(tokens[i],"standard_parallel_2",false))
			par.stlat2 = atof(tokens[++i].c_str())*TeCDR;
 		else if (TeStringCompare(tokens[i],"false_easting",false))
			par.offx = atof(tokens[++i].c_str());
 		else if (TeStringCompare(tokens[i],"false_northing",false))
			par.offy = atof(tokens[++i].c_str());
 		else if (TeStringCompare(tokens[i],"scale_factor",false))
			par.scale = atof(tokens[++i].c_str());
	}
	return TeProjectionFactory::make(par);
}

