/******************************************************************************
 *
 * Purpose:  Implementation of the CPCIDSKGeoref class.
 * 
 ******************************************************************************
 * Copyright (c) 2009
 * PCI Geomatics, 50 West Wilmot Street, Richmond Hill, Ont, Canada
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "pcidsk_exception.h"
#include "segment/cpcidskgeoref.h"
#include "core/pcidsk_utils.h"
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <cstdio>
#include <cctype>

using namespace PCIDSK;

static double PAK2PCI( double deg, int function );

#ifndef ABS
#  define ABS(x)        ((x<0) ? (-1*(x)) : x)
#endif

/************************************************************************/
/*                           CPCIDSKGeoref()                            */
/************************************************************************/

CPCIDSKGeoref::CPCIDSKGeoref( PCIDSKFile *file, int segment,
                              const char *segment_pointer )
        : CPCIDSKSegment( file, segment, segment_pointer )

{
    loaded = false;
}

/************************************************************************/
/*                           ~CPCIDSKGeoref()                           */
/************************************************************************/

CPCIDSKGeoref::~CPCIDSKGeoref()

{
}

/************************************************************************/
/*                                Load()                                */
/************************************************************************/

void CPCIDSKGeoref::Load()

{
    if( loaded )
        return;

    // TODO: this should likely be protected by a mutex. 

/* -------------------------------------------------------------------- */
/*      Load the segment contents into a buffer.                        */
/* -------------------------------------------------------------------- */
    seg_data.SetSize( (int) (data_size - 1024) );

    ReadFromFile( seg_data.buffer, 0, data_size - 1024 );

/* -------------------------------------------------------------------- */
/*      Handle simple case of a POLYNOMIAL.                             */
/* -------------------------------------------------------------------- */
    if( strncmp(seg_data.buffer,"POLYNOMIAL",10) == 0 )
    {
        seg_data.Get(32,16,geosys);
        
        if( seg_data.GetInt(48,8) != 3 || seg_data.GetInt(56,8) != 3 )
            ThrowPCIDSKException( "Unexpected number of coefficients in POLYNOMIAL GEO segment." );

        a1   = seg_data.GetDouble(212+26*0,26);
        a2   = seg_data.GetDouble(212+26*1,26);
        xrot = seg_data.GetDouble(212+26*2,26);

        b1   = seg_data.GetDouble(1642+26*0,26);
        yrot = seg_data.GetDouble(1642+26*1,26);
        b3   = seg_data.GetDouble(1642+26*2,26);
    }

/* -------------------------------------------------------------------- */
/*      Handle the case of a PROJECTION segment - for now we ignore     */
/*      the actual projection parameters.                               */
/* -------------------------------------------------------------------- */
    else if( strncmp(seg_data.buffer,"PROJECTION",10) == 0 )
    {
        seg_data.Get(32,16,geosys);
        
        if( seg_data.GetInt(48,8) != 3 || seg_data.GetInt(56,8) != 3 )
            ThrowPCIDSKException( "Unexpected number of coefficients in POLYNOMIAL GEO segment." );

        a1   = seg_data.GetDouble(1980+26*0,26);
        a2   = seg_data.GetDouble(1980+26*1,26);
        xrot = seg_data.GetDouble(1980+26*2,26);

        b1   = seg_data.GetDouble(2526+26*0,26);
        yrot = seg_data.GetDouble(2526+26*1,26);
        b3   = seg_data.GetDouble(2526+26*2,26);
    }

/* -------------------------------------------------------------------- */
/*      Blank segment, just created and we just initialize things a bit.*/
/* -------------------------------------------------------------------- */
    else if( memcmp(seg_data.buffer,
                    "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0",16) == 0 )
    {
        geosys = "";
        
        a1 = 0.0;
        a2 = 1.0;
        xrot = 0.0;
        b1 = 0.0;
        yrot = 0.0;
        b3 = 1.0;
    }

    else
    {
        ThrowPCIDSKException( "Unexpected GEO segment type: %s", 
                              seg_data.Get(0,16) );
    }
}

/************************************************************************/
/*                             GetGeosys()                              */
/************************************************************************/

std::string CPCIDSKGeoref::GetGeosys()

{
    Load();
    return geosys;
}

/************************************************************************/
/*                            GetTransform()                            */
/************************************************************************/

void CPCIDSKGeoref::GetTransform( double &a1, double &a2, double &xrot, 
                                  double &b1, double &yrot, double &b3 )

{
    Load();

    a1   = this->a1;
    a2   = this->a2;
    xrot = this->xrot;
    b1   = this->b1;
    yrot = this->yrot;
    b3   = this->b3;
}

/************************************************************************/
/*                           GetParameters()                            */
/************************************************************************/

std::vector<double> CPCIDSKGeoref::GetParameters()

{
    unsigned int  i;
    std::vector<double> parms;

    Load();

    parms.resize(18);

    if( strncmp(seg_data.buffer,"PROJECTION",10) != 0 )
    {
        for( i = 0; i < 17; i++ )
            parms[i] = 0.0;
        parms[17] = -1.0;
    }
    else
    {
        for( i = 0; i < 17; i++ )
            parms[i] = seg_data.GetDouble(80+26*i,26);

        std::string grid_units;
        seg_data.Get(64,16,grid_units);

        if( EQUALN(grid_units.c_str(),"DEGREE",3) )
            parms[17] = (double) (int) UNIT_DEGREE;
        else if( EQUALN(grid_units.c_str(),"MET",3) )
            parms[17] = (double) (int) UNIT_METER;
        else if( EQUALN(grid_units.c_str(),"FOOT",4) )
            parms[17] = (double) (int) UNIT_US_FOOT;
        else if( EQUALN(grid_units.c_str(),"FEET",4) )
            parms[17] = (double) (int) UNIT_US_FOOT;
        else if( EQUALN(grid_units.c_str(),"INTL FOOT",5) )
            parms[17] = (double) (int) UNIT_INTL_FOOT;
        else
            parms[17] = -1.0; /* unknown */
    }

    return parms;
}

/************************************************************************/
/*                            WriteSimple()                             */
/************************************************************************/

void CPCIDSKGeoref::WriteSimple( std::string geosys, 
                                 double a1, double a2, double xrot, 
                                 double b1, double yrot, double b3 )

{
    Load();

    ReformatGeosys( geosys );

/* -------------------------------------------------------------------- */
/*      Establish the appropriate units code when possible.             */
/* -------------------------------------------------------------------- */
    std::string units_code = "METER";

    if( EQUALN(geosys.c_str(),"FOOT",4) )
        units_code = "FOOT";
    else if( EQUALN(geosys.c_str(),"SPAF",4) )
        units_code = "FOOT";
    else if( EQUALN(geosys.c_str(),"SPIF",4) )
        units_code = "INTL FOOT";
    else if( EQUALN(geosys.c_str(),"LONG",4) )
        units_code = "DEEGREE";
        
/* -------------------------------------------------------------------- */
/*      Write a fairly simple PROJECTION segment.                       */
/* -------------------------------------------------------------------- */
    seg_data.SetSize( 6 * 512 );

    seg_data.Put( " ", 0, seg_data.buffer_size );

    // SD.PRO.P1
    seg_data.Put( "PROJECTION", 0, 16 );
    
    // SD.PRO.P2
    seg_data.Put( "PIXEL", 16, 16 );
    
    // SD.PRO.P3
    seg_data.Put( geosys.c_str(), 32, 16 );

    // SD.PRO.P4
    seg_data.Put( 3, 48, 8 );
    
    // SD.PRO.P5
    seg_data.Put( 3, 56, 8 );

    // SD.PRO.P6 
    seg_data.Put( units_code.c_str(), 64, 16 );

    // SD.PRO.P7 - P22
    for( int i = 0; i < 17; i++ )
        seg_data.Put( 0.0,   80 + i*26, 26, "%26.18E" );
    
    // SD.PRO.P24
    PrepareGCTPFields();

    // SD.PRO.P26
    seg_data.Put( a1,  1980 + 0*26, 26, "%26.18E" );
    seg_data.Put( a2,  1980 + 1*26, 26, "%26.18E" );
    seg_data.Put( xrot,1980 + 2*26, 26, "%26.18E" );

    // SD.PRO.P27
    seg_data.Put( b1,   2526 + 0*26, 26, "%26.18E" );
    seg_data.Put( yrot, 2526 + 1*26, 26, "%26.18E" );
    seg_data.Put( b3,   2526 + 2*26, 26, "%26.18E" );

    WriteToFile( seg_data.buffer, 0, seg_data.buffer_size );

    loaded = false;
}

/************************************************************************/
/*                          WriteParameters()                           */
/************************************************************************/

void CPCIDSKGeoref::WriteParameters( std::vector<double> &parms )

{
    Load();

    if( parms.size() < 17 )
        ThrowPCIDSKException( "Did not get expected number of paramters in WriteParameters()" );

    unsigned int i;

    for( i = 0; i < 17; i++ )
        seg_data.Put(parms[i],80+26*i,26,"%26.16f");

    if( parms.size() >= 18 )
    {
        switch( (UnitCode) (int) parms[17] ) 
        {
          case UNIT_DEGREE:
            seg_data.Put( "DEGREE", 64, 16 );
            break;

          case UNIT_METER:
            seg_data.Put( "METER", 64, 16 );
            break;

          case UNIT_US_FOOT:
            seg_data.Put( "FOOT", 64, 16 );
            break;

          case UNIT_INTL_FOOT:
            seg_data.Put( "INTL FOOT", 64, 16 );
            break;
        }
    }

    PrepareGCTPFields();

    WriteToFile( seg_data.buffer, 0, seg_data.buffer_size );

    // no need to mark loaded false, since we don't cache these parameters.
}

/************************************************************************/
/*                         GetUSGSParameters()                          */
/************************************************************************/

std::vector<double> CPCIDSKGeoref::GetUSGSParameters()

{
    unsigned int  i;
    std::vector<double> parms;

    Load();

    parms.resize(19);
    if( strncmp(seg_data.buffer,"PROJECTION",10) != 0 )
    {
        for( i = 0; i < 19; i++ )
            parms[i] = 0.0;
    }
    else
    {
        for( i = 0; i < 19; i++ )
            parms[i] = seg_data.GetDouble(1458+26*i,26);
    }

    return parms;
}

/************************************************************************/
/*                           ReformatGeosys()                           */
/*                                                                      */
/*      Put a geosys string into standard form.  Similar to what the    */
/*      DecodeGeosys() function in the PCI SDK does.                    */
/************************************************************************/

void CPCIDSKGeoref::ReformatGeosys( std::string &geosys )

{
/* -------------------------------------------------------------------- */
/*      Put into a local buffer and pad out to 16 characters with       */
/*      spaces.                                                         */
/* -------------------------------------------------------------------- */
    char local_buf[33];

    strncpy( local_buf, geosys.c_str(), 16 );
    local_buf[16] = '\0';
    strcat( local_buf, "                " );
    local_buf[16] = '\0';
    
/* -------------------------------------------------------------------- */
/*      Extract the earth model from the geosys string.                 */
/* -------------------------------------------------------------------- */
    char earthmodel[5];
    const char	*cp;
    int		i;
    char	last;

    cp = local_buf;
    while( cp < local_buf + 16 && cp[1] != '\0' )
        cp++;

    while( cp > local_buf && isspace(*cp) )
        cp--;

    last = '\0';
    while( cp > local_buf 
           && (isdigit((unsigned char)*cp)
               || *cp == '-' || *cp == '+' ) )
    {
        if( last == '\0' )  last = *cp;
        cp--;
    }

    if( isdigit( (unsigned char)last ) &&
        ( *cp == 'D' || *cp == 'd' ||
          *cp == 'E' || *cp == 'e'    ) )
    {
        i = atoi( cp+1 );
        if(    i > -100 && i < 1000 
               && (cp == local_buf
                   || ( cp >  local_buf && isspace( *(cp-1) ) )
                   )
               )
        {
            if( *cp == 'D' || *cp == 'd' )
                sprintf( earthmodel, "D%03d", i );
            else
                sprintf( earthmodel, "E%03d", i );
        }
        else
        {
            sprintf( earthmodel, "    " );
        }
    }
    else
    {
        sprintf( earthmodel, "    " );
    }

/* -------------------------------------------------------------------- */
/*      Identify by geosys string.                                      */
/* -------------------------------------------------------------------- */
    const char *ptr;
    int zone, ups_zone;
    char zone_code = ' ';

    if( EQUALN(local_buf,"PIX",3) )
    {
        strcpy( local_buf, "PIXEL           " );
    }
    else if( EQUALN(local_buf,"UTM",3) )
    {
        /* Attempt to find a zone and ellipsoid */
        for( ptr=local_buf+3; isspace(*ptr); ptr++ ) {}
        if( isdigit( (unsigned char)*ptr ) || *ptr == '-' )
        {
            zone = atoi(ptr);
            for( ; isdigit((unsigned char)*ptr) || *ptr == '-'; ptr++ ) {}
            for( ; isspace(*ptr); ptr++ ) {}
            if( isalpha(*ptr) && !isdigit((unsigned char)*(ptr+1)) )
                zone_code = *(ptr++);
        }
        else
            zone = -100;

        if( zone >= -60 && zone <= 60 && zone != 0 )
        {
            if( zone_code >= 'a' && zone_code <= 'z' )
                zone_code = zone_code - 'a' + 'A';

            if( zone_code == ' ' && zone < 0 )
                zone_code = 'C';

            zone = ABS(zone);

            sprintf( local_buf,
                     "UTM   %3d %c %4s", 
                     zone, zone_code, earthmodel );
        }
        else
        {	
            sprintf( local_buf, 
                     "UTM         %4s", 
                     earthmodel );
        }
        if( local_buf[14] == ' ' )
            local_buf[14] = '0';
        if( local_buf[13] == ' ' )
            local_buf[13] = '0';
    }
    else if( EQUALN(local_buf,"MET",3) )
    {
        sprintf( local_buf, "METRE       %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"FEET",4) || EQUALN(local_buf,"FOOT",4))
    {
        sprintf( local_buf, "FOOT        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"LAT",3) ||
             EQUALN(local_buf,"LON",3) )
    {
        sprintf( local_buf, 
                 "LONG/LAT    %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"SPCS ",5) ||
             EQUALN(local_buf,"SPAF ",5) ||
             EQUALN(local_buf,"SPIF ",5) )
    {
        int nSPZone = 0;

        for( ptr=local_buf+4; isspace(*ptr); ptr++ ) {}
        nSPZone = atoi(ptr);

        if      ( EQUALN(local_buf,"SPCS ",5) ) 
            strcpy( local_buf, "SPCS " );
        else if ( EQUALN(local_buf,"SPAF ",5) ) 
            strcpy( local_buf, "SPAF " );
        else
            strcpy( local_buf, "SPIF " );

        if( nSPZone != 0 )
            sprintf( local_buf + 5, "%4d   %4s",nSPZone,earthmodel);
        else
            sprintf( local_buf + 5, "       %4s",earthmodel);

    }
    else if( EQUALN(local_buf,"ACEA ",5) )
    {
        sprintf( local_buf, 
                 "ACEA        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"AE ",3) )
    {
        sprintf( local_buf, 
                 "AE          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"EC ",3) )
    {
        sprintf( local_buf, 
                 "EC          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"ER ",3) )
    {
        sprintf( local_buf, 
                 "ER          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"GNO ",4) )
    {
        sprintf( local_buf, 
                 "GNO         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"GVNP",4) )
    {
        sprintf( local_buf, 
                 "GVNP        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"LAEA_ELL",8) )
    {
        sprintf( local_buf, 
                 "LAEA_ELL    %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"LAEA",4) )
    {
        sprintf( local_buf, 
                 "LAEA        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"LCC_1SP",7) )
    {
        sprintf( local_buf, 
                 "LCC_1SP     %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"LCC ",4) )
    {
        sprintf( local_buf, 
                 "LCC         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"MC ",3) )
    {
        sprintf( local_buf, 
                 "MC          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"MER ",4) )
    {
        sprintf( local_buf, 
                 "MER         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"MSC ",4) )
    {
        sprintf( local_buf, 
                 "MSC         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"OG ",3) )
    {
        sprintf( local_buf, 
                 "OG          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"OM ",3) )
    {
        sprintf( local_buf, 
                 "OM          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"PC ",3) )
    {
        sprintf( local_buf, 
                 "PC          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"PS ",3) )
    {
        sprintf( local_buf, 
                 "PS          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"ROB ",4) )
    {
        sprintf( local_buf, 
                 "ROB         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"SG ",3) )
    {
        sprintf( local_buf, 
                 "SG          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"SIN ",4) )
    {
        sprintf( local_buf, 
                 "SIN         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"SOM ",4) )
    {
        sprintf( local_buf, 
                 "SOM         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"TM ",3) )
    {
        sprintf( local_buf, 
                 "TM          %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"VDG ",4) )
    {
        sprintf( local_buf, 
                 "VDG         %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"UPSA",4) )
    {
        sprintf( local_buf, 
                 "UPSA        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"UPS ",4) )
    {
        /* Attempt to find UPS zone */
        for( ptr=local_buf+3; isspace(*ptr); ptr++ ) {}
        if( *ptr == 'A' || *ptr == 'B' || *ptr == 'Y' || *ptr == 'Z' )
            ups_zone = *ptr;
        else if( *ptr == 'a' || *ptr == 'b' || *ptr == 'y' || *ptr == 'z' )
            ups_zone = toupper( *ptr );
        else
            ups_zone = ' ';

        sprintf( local_buf, 
                 "UPS       %c %4s",
                 ups_zone, earthmodel );
    }
    else if( EQUALN(local_buf,"GOOD",4) )
    {
        sprintf( local_buf, 
                 "GOOD        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"NZMG",4) )
    {
        sprintf( local_buf, 
                 "NZMG        %4s",
                 earthmodel );
    }
    else if( EQUALN(local_buf,"CASS",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "CASS        %4s", "E010" );
        else
            sprintf( local_buf,  "CASS        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"RSO ",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "RSO         %4s", "E010" );
        else
            sprintf( local_buf,  "RSO         %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"KROV",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "KROV        %4s", "E002" );
        else
            sprintf( local_buf,  "KROV        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"KRON",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "KRON        %4s", "E002" );
        else
            sprintf( local_buf,  "KRON        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"SGDO",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "SGDO        %4s", "E910" );
        else
            sprintf( local_buf,  "SGDO        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"LBSG",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "LBSG        %4s", "E202" );
        else
            sprintf( local_buf,  "LBSG        %4s", earthmodel );
    }
    else if( EQUALN(local_buf,"ISIN",4) )
    {
        if( EQUALN( earthmodel, "D000", 4 ) )
            sprintf( local_buf,  "ISIN        %4s", "E700" );
        else
            sprintf( local_buf,  "ISIN        %4s", earthmodel );
    }
/* -------------------------------------------------------------------- */
/*      This may be a user projection. Just reformat the earth model    */
/*      portion.                                                        */
/* -------------------------------------------------------------------- */
    else
    {
        sprintf( local_buf, "%-11.11s %4s", geosys.c_str(), earthmodel );
    }

    geosys = local_buf;
}

/*
C       PAK2PCI converts a Latitude or Longitude value held in decimal
C       form to or from the standard packed DMS format DDDMMMSSS.SSS.
C       The standard packed DMS format is the required format for any
C       Latitude or Longitude value in the projection parameter array
C       (TPARIN and/or TPARIO) in calling the U.S.G.S. GCTP package,
C       but is not required for the actual coordinates to be converted.
C	This routine has been coverted from the PAKPCI fortran routine.
C
C       When function is 1, the value returned is made up as follows:
C
C       PACK2PCI = (DDD * 1000000) + (MMM * 1000) + SSS.SSS
C
C       When function is 0, the value returned is made up as follows:
C
C       PACK2PCI = DDD + MMM/60 + SSS/3600
C
C       where:   DDD     are the degrees
C                MMM     are the minutes
C                SSS.SSS are the seconds
C
C       The sign of the input value is retained and will denote the
C       hemisphere (For longitude, (-) is West and (+) is East of
C       Greenwich;  For latitude, (-) is South and (+) is North of
C       the equator).
C
C
C       CALL SEQUENCE
C
C       double = PACK2PCI (degrees, function)
C
C       degrees  - (double) Latitude or Longitude value in decimal 
C	                    degrees.
C
C       function - (Int)    Function to perform                            
C                           1, convert decimal degrees to DDDMMMSSS.SSS
C                           0, convert DDDMMMSSS.SSS to decimal degrees
C
C
C       EXAMPLE
C
C       double		degrees, packed, unpack 
C
C       degrees = -125.425              ! Same as 125d 25' 30" W
C       packed = PACK2PCI (degrees, 1)  ! PACKED will equal -125025030.000
C       unpack = PACK2PCI (degrees, 0)  ! UNPACK will equal -125.425
*/

/************************************************************************/
/*                              PAK2PCI()                               */
/************************************************************************/

static double PAK2PCI( double deg, int function )
{
        double    new_deg;
        int       sign;
        double    result;

	double	  degrees;
        double    temp1, temp2, temp3;
        int       dd, mm;
        double	  ss;

        sign = (int)(1.0);
	degrees = deg;

        if ( degrees < 0 )
        {
           sign = (int)(-1.0);
           degrees = degrees * sign;
        }

/* -------------------------------------------------------------------- */
/*	Unpack the value.						*/
/* -------------------------------------------------------------------- */
        if ( function == 0 )
        {
           new_deg = (double) ABS( degrees );

           dd =  (int)( new_deg / 1000000.0);

           new_deg = ( new_deg - (dd * 1000000) );
           mm = (int)(new_deg/(1000));

           new_deg = ( new_deg - (mm * 1000) );

           ss = new_deg;

           result = (double) sign * ( dd + mm/60.0 + ss/3600.0 );
        }
        else
        {
/* -------------------------------------------------------------------- */
/*	Procduce DDDMMMSSS.SSS from decimal degrees.			*/
/* -------------------------------------------------------------------- */
           new_deg = (double) ((int)degrees % 360);
           temp1 =  degrees - new_deg;

           temp2 = temp1 * 60;

           mm = (int)((temp2 * 60) / 60);

           temp3 = temp2 - mm;
           ss = temp3 * 60;

           result = (double) sign *
                ( (new_deg * 1000000) + (mm * 1000) + ss);
        }
	return result;
}

/************************************************************************/
/*                         PrepareGCTPFields()                          */
/*                                                                      */
/*      Fill the GCTP fields in the seg_data image based on the         */
/*      non-GCTP values.                                                */
/************************************************************************/

void CPCIDSKGeoref::PrepareGCTPFields()

{
    enum GCTP_UNIT_CODES {
        GCTP_UNIT_UNKNOWN = -1, /*    Default, NOT a valid code     */
        GCTP_UNIT_RADIAN  =  0, /* 0, NOT used at present           */
        GCTP_UNIT_US_FOOT,      /* 1, Used for GEO_SPAF             */
        GCTP_UNIT_METRE,        /* 2, Used for most map projections */
        GCTP_UNIT_SECOND,       /* 3, NOT used at present           */
        GCTP_UNIT_DEGREE,       /* 4, Used for GEO_LONG             */
        GCTP_UNIT_INTL_FOOT,    /* 5, Used for GEO_SPIF             */
        GCTP_UNIT_TABLE         /* 6, NOT used at present           */
    };

    seg_data.Get(32,16,geosys);
    ReformatGeosys( geosys );

/* -------------------------------------------------------------------- */
/*      Establish the GCTP units code.                                  */
/* -------------------------------------------------------------------- */
    double IOmultiply = 1.0;
    int UnitsCode = GCTP_UNIT_METRE;

    std::string grid_units;
    seg_data.Get(64,16,grid_units);

    if( EQUALN(grid_units.c_str(),"MET",3) )
        UnitsCode = GCTP_UNIT_METRE;
    else if( EQUALN( grid_units.c_str(), "FOOT", 4 ) )
    {
        UnitsCode = GCTP_UNIT_US_FOOT;
        IOmultiply = 1.0 / 0.3048006096012192;
    }
    else if( EQUALN( grid_units.c_str(), "INTL FOOT", 9 ) )
    {
        UnitsCode = GCTP_UNIT_INTL_FOOT;
        IOmultiply = 1.0 / 0.3048;
    }
    else if( EQUALN( grid_units.c_str(), "DEGREE", 6 ) )
        UnitsCode = GCTP_UNIT_DEGREE;
    
/* -------------------------------------------------------------------- */
/*      Extract the non-GCTP style parameters.                          */
/* -------------------------------------------------------------------- */
    double pci_parms[17];
    int i;

    for( i = 0; i < 17; i++ )
        pci_parms[i] = seg_data.GetDouble(80+26*i,26);

#define Dearth0                 pci_parms[0]
#define Dearth1                 pci_parms[1]
#define RefLong                 pci_parms[2]
#define RefLat                  pci_parms[3]
#define StdParallel1            pci_parms[4]
#define StdParallel2            pci_parms[5]
#define FalseEasting            pci_parms[6]
#define FalseNorthing           pci_parms[7]
#define Scale                   pci_parms[8]
#define Height                  pci_parms[9]
#define Long1                   pci_parms[10]
#define Lat1                    pci_parms[11]
#define Long2                   pci_parms[12]
#define Lat2                    pci_parms[13]
#define Azimuth                 pci_parms[14]
#define LandsatNum              pci_parms[15]
#define LandsatPath             pci_parms[16]

/* -------------------------------------------------------------------- */
/*      Get the zone code.                                              */
/* -------------------------------------------------------------------- */
    int ProjectionZone = 0;

    if( strncmp(geosys.c_str(),"UTM ",4) == 0 
        || strncmp(geosys.c_str(),"SPCS ",5) == 0 
        || strncmp(geosys.c_str(),"SPAF ",5) == 0 
        || strncmp(geosys.c_str(),"SPIF ",5) == 0 )
    {
        ProjectionZone = atoi(geosys.c_str() + 5);
    }

/* -------------------------------------------------------------------- */
/*      Handle the ellipsoid.  We depend on applications properly       */
/*      setting proj_parms[0], and proj_parms[1] with the semi-major    */
/*      and semi-minor axes in all other cases.                         */
/*                                                                      */
/*      I wish we could lookup datum codes to find their GCTP           */
/*      ellipsoid values here!                                          */
/* -------------------------------------------------------------------- */
    int Spheroid = -1;
    if( geosys[12] == 'E' )
        Spheroid = atoi(geosys.c_str() + 13);
    
    if( Spheroid < 0 || Spheroid > 19 )
        Spheroid = -1;

/* -------------------------------------------------------------------- */
/*      Initialize the USGS Parameters.                                 */
/* -------------------------------------------------------------------- */
    double USGSParms[15];
    int gsys; 

    for ( i = 0; i < 15; i++ )
        USGSParms[i] = 0;
	
/* -------------------------------------------------------------------- */
/*	Projection 0: Geographic (no projection)			*/
/* -------------------------------------------------------------------- */
    if( strncmp(geosys.c_str(),"LONG ",5) == 0 )
    {
        gsys = 0;
        UnitsCode = GCTP_UNIT_DEGREE;
    }

/* -------------------------------------------------------------------- */
/*	Projection 1: Universal Transverse Mercator			*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"UTM ",4) == 0 )
    {
        char row_char = geosys[10];
        gsys = 1;

        // Southern hemisphere?
        if( (row_char >= 'C') && (row_char <= 'M') && ProjectionZone > 0 )
        {
            ProjectionZone *= -1;
        }

/* -------------------------------------------------------------------- */
/*	Process UTM as TM.  The reason for this is the GCTP software	*/
/*	does not provide for input of an Earth Model for UTM, but does	*/
/*	for TM.								*/
/* -------------------------------------------------------------------- */
        gsys = 9;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = 0.9996;

        USGSParms[4] = PAK2PCI(
            ( ABS(ProjectionZone) * 6.0 - 183.0 ), 1 );
        USGSParms[5] = PAK2PCI( 0.0, 1 );
        USGSParms[6] =   500000.0;
        USGSParms[7] = ( ProjectionZone < 0 ) ? 10000000.0 : 0.0;
    }

/* -------------------------------------------------------------------- */
/*	Projection 2: State Plane Coordinate System			*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"SPCS ",5) == 0 )
    {
        gsys = 2;
        if(    UnitsCode != GCTP_UNIT_METRE
               && UnitsCode != GCTP_UNIT_US_FOOT
               && UnitsCode != GCTP_UNIT_INTL_FOOT )
            UnitsCode = GCTP_UNIT_METRE;
    }

    else if( strncmp(geosys.c_str(),"SPAF ",5) == 0 )
    {
        gsys = 2;
        if(    UnitsCode != GCTP_UNIT_METRE
               && UnitsCode != GCTP_UNIT_US_FOOT
               && UnitsCode != GCTP_UNIT_INTL_FOOT )
            UnitsCode = GCTP_UNIT_US_FOOT;
    }

    else if( strncmp(geosys.c_str(),"SPIF ",5) == 0 )
    {
        gsys = 2;
        if(    UnitsCode != GCTP_UNIT_METRE
               && UnitsCode != GCTP_UNIT_US_FOOT
               && UnitsCode != GCTP_UNIT_INTL_FOOT )
            UnitsCode = GCTP_UNIT_INTL_FOOT;
    }

/* -------------------------------------------------------------------- */
/*	Projection 3: Albers Conical Equal-Area 			*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"ACEA ",5) == 0 )
    {
        gsys = 3;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = PAK2PCI(StdParallel1, 1);
        USGSParms[3] = PAK2PCI(StdParallel2, 1);
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 4: Lambert Conformal Conic				*/ 
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"LCC  ",5) == 0 )
    {
        gsys = 4;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = PAK2PCI(StdParallel1, 1);
        USGSParms[3] = PAK2PCI(StdParallel2, 1);
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 5: Mercator						*/ 
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"MER  ",5) == 0 )
    {
        gsys = 5;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 6: Polar Stereographic		 		*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"PS   ",5) == 0 )
    {
        gsys = 6;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 7: Polyconic			 			*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"PC   ",5) == 0 )
    {
        gsys = 7;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 8: Equidistant Conic                     		*/
/*	Format A, one standard parallel,  usgs_params[8] = 0		*/
/*      Format B, two standard parallels, usgs_params[8] = not 0	*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"EC   ",5) == 0 )
    {
        gsys = 8;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = PAK2PCI(StdParallel1, 1);
        USGSParms[3] = PAK2PCI(StdParallel2, 1);
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;

        if ( StdParallel2 != 0 )
        {
            USGSParms[8] = 1;
        }
    }	

/* -------------------------------------------------------------------- */
/*	Projection 9: Transverse Mercator				*/ 
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"TM   ",5) == 0 )
    {
        gsys = 9;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = Scale;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 10: Stereographic					*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"SG   ",5) == 0 )
    {
        gsys = 10;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	
    
/* -------------------------------------------------------------------- */
/*	Projection 11: Lambert Azimuthal Equal-Area			*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"LAEA ",5) == 0 )
    {
        gsys = 11;
        
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 12: Azimuthal Equidistant				*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"AE   ",5) == 0 )
    {
        gsys = 12;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 13: Gnomonic						*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"GNO  ",5) == 0 )
    {
        gsys = 13;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 14: Orthographic					*/ 
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"OG   ",5) == 0 )
    {
        gsys = 14;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection  15: General Vertical Near-Side Perspective		*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"GVNP ",5) == 0 )
    {
        gsys = 15;
        USGSParms[0] = Dearth0;
        
        USGSParms[2] = Height;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
      }	

/* -------------------------------------------------------------------- */
/*	Projection 16: Sinusoidal 					*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"SIN  ",5) == 0 )
    {
        gsys = 16;
        USGSParms[0] = Dearth0;
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 17: Equirectangular					*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"ER   ",5) == 0 )
    {
        gsys = 17;
        USGSParms[0] = Dearth0;
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	
/* -------------------------------------------------------------------- */
/*	Projection 18: Miller Cylindrical				*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"MC   ",5) == 0 )
    {
        gsys = 18;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 19: Van der Grinten					*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"VDG  ",5) == 0 )
    {
        gsys = 19;
        USGSParms[0] = Dearth0;
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 20:  Oblique Mercator (Hotine)			*/
/*	  Format A, Azimuth and RefLong defined (Long1, Lat1,      	*/
/*	     Long2, Lat2 not defined), usgs_params[12] = 0      	*/
/*	  Format B, Long1, Lat1, Long2, Lat2 defined (Azimuth		*/
/*	     and RefLong not defined), usgs_params[12] = not 0      	*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"OM   ",5) == 0 )
    {
        gsys = 20;
        USGSParms[0] = Dearth0;
        USGSParms[1] = Dearth1;
        USGSParms[2] = Scale;
        USGSParms[3] = PAK2PCI(Azimuth ,1);
        
        USGSParms[4] = PAK2PCI(RefLong, 1);
        USGSParms[5] = PAK2PCI(RefLat, 1);
        USGSParms[6] = FalseEasting * IOmultiply;
        USGSParms[7] = FalseNorthing * IOmultiply;
        
        USGSParms[8] = PAK2PCI(Long1, 1);
        USGSParms[9] = PAK2PCI(Lat1, 1);
        USGSParms[10] = PAK2PCI(Long2, 1);
        USGSParms[11] = PAK2PCI(Lat2, 1);
        if ( (Long1 != 0) || (Lat1 != 0) ||
             (Long2 != 0) || (Lat2 != 0)    )
            USGSParms[12] = 0.0;
        else
            USGSParms[12] = 1.0;
    }	
/* -------------------------------------------------------------------- */
/*	Projection 21: Robinson						*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"ROB  ",5) == 0 )
    {
          gsys = 21;
          USGSParms[0] = Dearth0;

          USGSParms[4] = PAK2PCI(RefLong, 1);
          USGSParms[6] = FalseEasting
              * IOmultiply;
          USGSParms[7] = FalseNorthing
              * IOmultiply;

      }	
/* -------------------------------------------------------------------- */
/*	Projection 22: Space Oblique Mercator				*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"SOM  ",5) == 0 )
    {
          gsys = 22;
          USGSParms[0] = Dearth0;
          USGSParms[1] = Dearth1;
          USGSParms[2] = LandsatNum;
          USGSParms[3] = LandsatPath;
          USGSParms[6] = FalseEasting
              * IOmultiply;
          USGSParms[7] = FalseNorthing
              * IOmultiply;
    }	
/* -------------------------------------------------------------------- */
/*	Projection 23: Modified Stereographic Conformal (Alaska)	*/ 
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"MSC  ",5) == 0 )
    {
          gsys = 23;
          USGSParms[0] = Dearth0;
          USGSParms[1] = Dearth1;
          USGSParms[6] = FalseEasting
              * IOmultiply;
          USGSParms[7] = FalseNorthing
              * IOmultiply;
    }	

/* -------------------------------------------------------------------- */
/*	Projection 6: Universal Polar Stereographic is just Polar	*/
/*	Stereographic with certain assumptions.				*/
/* -------------------------------------------------------------------- */
    else if( strncmp(geosys.c_str(),"UPS  ",5) == 0 )
    {
          gsys = 6;

          USGSParms[0] = Dearth0;
          USGSParms[1] = Dearth1;

          USGSParms[4] = PAK2PCI(0.0, 1);

          USGSParms[6] = 2000000.0;
          USGSParms[7] = 2000000.0;

          double dwork = 81.0 + 6.0/60.0 + 52.3/3600.0;

          if( geosys[10] == 'A' || geosys[10] == 'B' )
          {
              USGSParms[5] = PAK2PCI(-dwork,1);
          }
          else if( geosys[10] == 'Y' || geosys[10]=='Z')
          {
              USGSParms[5] = PAK2PCI(dwork,1);
          }
          else
          {
              USGSParms[4] = PAK2PCI(RefLong, 1);
              USGSParms[5] = PAK2PCI(RefLat, 1);
              USGSParms[6] = FalseEasting
                  * IOmultiply;
              USGSParms[7] = FalseNorthing
                  * IOmultiply;
          }
      }	

/* -------------------------------------------------------------------- */
/*	Unknown code.							*/
/* -------------------------------------------------------------------- */
    else
    {
        gsys = -1;
    }

    if( ProjectionZone == 0 )
        ProjectionZone = 10000 + gsys;

/* -------------------------------------------------------------------- */
/*      Place USGS values in the formatted segment.                     */
/* -------------------------------------------------------------------- */
    seg_data.Put( (double) gsys,           1458   , 26, "%26.18lE" );
    seg_data.Put( (double) ProjectionZone, 1458+26, 26, "%26.18lE" );

    for( i = 0; i < 15; i++ )
        seg_data.Put( USGSParms[i], 1458+26*(2+i), 26, "%26.18lE" );

    seg_data.Put( (double) UnitsCode, 1458+26*17, 26, "%26.18lE" );
    seg_data.Put( (double) Spheroid,  1458+26*18, 26, "%26.18lE" );
}


