////////////////////////////////////
//  C++ による 地図投影法のクラス //
////////////////////////////////////


#include    "Projection.h"
#include    "stdlib.h"
#include    "stdio.h"
#include    "string.h"
#include    "math.h"


/////////////////////////////////////////////////////////////////////////////
// Projectionクラスの構築子
Projection::Projection()
{
}


/////////////////////////////////////////////////////////////////////////////
// Projectionクラスの消滅子
Projection::~Projection()
{
}


/////////////////////////////////////////////////////////////////////////////
// 文字表現の緯度を実数値に変換して返す
// 89                   --> 89.000000
// 89.999999            --> 89.999999
// N89                  --> 89.000000
// N89.999999           --> 89.999999
// N89.59'              --> 89.983333
// N89.59'99            --> 89.999833
// N89.59'59"           --> 89.999722
// N89.59'59"99         --> 89.99999722
// -89                  --> -89.000000
// -89.999999           --> -89.999999
// S89                  --> -89.000000
// S89.999999           --> -89.999999
// S89.59'              --> -89.983333
// S89.59'99            --> -89.999833
// S89.59'59"           --> -89.999722
// S89.59'59"99         --> -89.99999722
double Projection::ConvLatitudeToDouble( char* Str )
{
    char    *p = Str;
    int     Sign = 1;           // Northern=1,  Southern=-1
    char    digits[64];
    int     cnt = 0;
    char    degrees[64];            // degree
    char    minutes[64];            // minute
    char    seconds[64];            // second
    BOOL    minutesFlg = FALSE;
    BOOL    secondsFlg = FALSE;
    BOOL    fructionMode = FALSE;
    double  value;              // latitude

    if( Str[0] == '\0' ){
        return INT_MAX;
    }
    memset( digits, 0, sizeof(digits) );
    memset( degrees, 0, sizeof(degrees) );
    memset( minutes, 0, sizeof(minutes) );
    memset( seconds, 0, sizeof(seconds) );
    while( *p != '\0' ){
        if( ( *p >= '0' )&&( *p <= '9' ) ){
            digits[cnt++] = *p;
        }else if( *p == CHAR_PUNCT ){
            if( ( minutesFlg == TRUE )||( secondsFlg == TRUE ) ){
                return INT_MAX;     // illegal fruction point!
            }
            strcpy( degrees, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == '\'' ){
            if( ( minutesFlg == TRUE )||( secondsFlg == TRUE ) ){
                return INT_MAX;     // illegal order!
            }
            minutesFlg = TRUE;
            strcpy( minutes, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == '\"' ){
            if( secondsFlg == TRUE ){
                return INT_MAX;     // illegal order!
            }
            secondsFlg = TRUE;
            strcpy( seconds, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == 'N' ){      // Northern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = 1;
        }else if( *p == 'S' ){      // Southern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = -1;
        }else if( *p == '+' ){      // Northern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = 1;
        }else if( *p == '-' ){      // Southern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = -1;
        }else if( ( *p == ' ' )||( *p == '\t' ) ){  // 空白
            if( cnt > 0 ){
                return INT_MAX;     // illegal space
            }
        }else{
            return INT_MAX;
        }
        p++;
    }
    if( cnt > 0 ){
        if( fructionMode == TRUE ){
            if( secondsFlg == TRUE ){
                seconds[strlen(seconds)] =  CHAR_PUNCT;
                strcat(seconds, digits );
            }else if( minutesFlg == TRUE ){
                minutes[strlen(minutes)] =  CHAR_PUNCT;
                strcat(minutes, digits );
            }else if( ( minutesFlg == FALSE )&&( secondsFlg == FALSE ) ){
                degrees[strlen(degrees)] =  CHAR_PUNCT;
                strcat(degrees, digits );
            }
        }else{
            if( ( minutesFlg == FALSE )&&( secondsFlg == FALSE ) ){
                strcpy( degrees, digits );
            }
        }
    }
    value = atof(degrees) + (atof(minutes) / 60 ) + ( atof(seconds) / 3600 );
    value *= Sign;
    return value;
}


/////////////////////////////////////////////////////////////////////////////
// 文字表現の経度を実数値に変換して返す
// 179                  --> 179.000000
// 179.999999           --> 179.999999
// E179                 --> 179.000000
// E179.999999          --> 179.999999
// E179.59'             --> 179.983333
// E179.59'99           --> 179.999833
// E179.59'59"          --> 179.999722
// E179.59'59"99        --> 179.99999722
// -179                 --> -179.000000
// -179.999999          --> -179.999999
// W179                 --> -179.000000
// W179.999999          --> -179.999999
// W179.59'             --> -179.983333
// W179.59'99           --> -179.999833
// W179.59'59"          --> -179.999722
// W179.59'59"99        --> -179.99999722
double Projection::ConvLongitudeToDouble( char* Str )
{
    char    *p = Str;
    int     Sign = 1;           // Eastern=1,  Western=-1
    char    digits[64];
    int     cnt = 0;
    char    degrees[64];            // degree
    char    minutes[64];            // minute
    char    seconds[64];            // second
    BOOL    minutesFlg = FALSE;
    BOOL    secondsFlg = FALSE;
    BOOL    fructionMode = FALSE;
    double  value;              // latitude

    if( Str[0] == '\0' ){
        return INT_MAX;
    }
    memset( digits, 0, sizeof(digits) );
    memset( degrees, 0, sizeof(degrees) );
    memset( minutes, 0, sizeof(minutes) );
    memset( seconds, 0, sizeof(seconds) );
    while( *p != '\0' ){
        if( ( *p >= '0' )&&( *p <= '9' ) ){
            digits[cnt++] = *p;
        }else if( *p == CHAR_PUNCT ){
            if( ( minutesFlg == TRUE )||( secondsFlg == TRUE ) ){
                return INT_MAX;     // illegal fruction point!
            }
            strcpy( degrees, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == '\'' ){
            if( ( minutesFlg == TRUE )||( secondsFlg == TRUE ) ){
                return INT_MAX;     // illegal order!
            }
            minutesFlg = TRUE;
            strcpy( minutes, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == '\"' ){
            if( secondsFlg == TRUE ){
                return INT_MAX;     // illegal order!
            }
            secondsFlg = TRUE;
            strcpy( seconds, digits );
            memset( digits, 0, sizeof(digits) );
            cnt = 0;
            fructionMode = TRUE;
        }else if( *p == 'E' ){      // Northern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = 1;
        }else if( *p == 'W' ){      // Southern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = -1;
        }else if( *p == '+' ){      // Eastern
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = 1;
        }else if( *p == '-' ){      // Western
            if( p != Str ){
                return INT_MAX;     // expect as top char!
            }
            Sign = -1;
        }else if( ( *p == ' ' )||( *p == '\t' ) ){  // 空白
            if( cnt > 0 ){
                return INT_MAX;     // illegal space
            }
        }else{
            return INT_MAX;
        }
        p++;
    }
    if( cnt > 0 ){
        if( fructionMode == TRUE ){
            if( secondsFlg == TRUE ){
                seconds[strlen(seconds)] =  CHAR_PUNCT;
                strcat(seconds, digits );
            }else if( minutesFlg == TRUE ){
                minutes[strlen(minutes)] =  CHAR_PUNCT;
                strcat(minutes, digits );
            }else if( ( minutesFlg == FALSE )&&( secondsFlg == FALSE ) ){
                degrees[strlen(degrees)] =  CHAR_PUNCT;
                strcat(degrees, digits );
            }
        }else{
            if( ( minutesFlg == FALSE )&&( secondsFlg == FALSE ) ){
                strcpy( degrees, digits );
            }
        }
    }
    value = atof(degrees) + (atof(minutes) / 60 ) + ( atof(seconds) / 3600 );
    value *= Sign;
    return value;
}


/////////////////////////////////////////////////////////////////////////////
// 経緯度をランベルト正積図法(Lambert Azimuthal equal-area projection)の座標系に変換
// IN/    lon  経度の実数値 (度単位)
// IN/    lat  緯度の実数値 (度単位)
// IN/    lon0 ランベルト正積図法の中心経度 (度単位)
// IN/    lat0 ランベルト正積図法の中心緯度 (度単位)
//   /OUT *x   求められたX座標  -1.0 〜 +1.0   地球の半径を掛けると実距離になる )
//   /OUT *y   求められたY座標  -1.0 〜 +1.0   地球の半径を掛けると実距離になる )
void Projection::GeoToLea( double lon, double lat, double lon0, double lat0, double *x, double *y )
{
    double  phi;        // latitude     radian
    double  phi1;
    double  lamda;      // longitude    radian
    double  lamda0;
    double pi = 3.14159265358979;
    double  k;

    phi = lat * pi / 180.0;
    phi1 = lat0  * pi / 180.0;
    lamda  = lon * pi / 180.0;
    lamda0  = lon0 * pi / 180.0;
    k = 2 / ( 1 + sin(phi1) * sin(phi) + cos(phi1) * cos(phi) * cos(lamda-lamda0) );
    if( k > 0 ){
        k = sqrt(k);
    }else{
        k = 0;
    }
    *x = k * cos(phi) * sin(lamda - lamda0);
    *y = k * ( cos(phi1) * sin(phi) - sin(phi1) * cos(phi) * cos(lamda-lamda0) );
}


/////////////////////////////////////////////////////////////////////////////
// LEA座標系をマップ画像の位置に変換
POINT Projection::ConvLeaToMapXy( double x, double y, double shift_x, double shift_y, double magnify )
{
    POINT pnt;

    pnt.x = (long)(( x + shift_x ) * magnify);
    pnt.y = (long)(( y * (-1) + shift_y) * magnify);

    return pnt;
}


/////////////////////////////////////////////////////////////////////////////
// メートル単位の距離をLEA座標系のピクセル数に換算
double Projection::lengthLEA( int length, double magnify )
{
    return  ( (double)length * magnify / 6365000.0 );
}


/////////////////////////////////////////////////////////////////////////////
// 経緯度を横メルカトル図法(Transverse Mercator's projection)の座標系に変換
// IN/    lon  経度の実数値 (度単位)
// IN/    lat  緯度の実数値 (度単位)
// IN/    lon0 横メルカトル図法の中心経度 (度単位)
//   /OUT *x   求められたX座標 中心経線からの距離  メートル単位)
//   /OUT *y   求められたY座標 赤道からの距離  メートル単位)
void Projection::GeoToTM( double lon, double lat, double lon0, double *x, double *y )
{
    double  phi;        // latitude     radian
    double  lamda;      // longitude    radian
    double  pi = 3.14159265358979;
    double  a = 6378137;
    double  f = 1 / 298.2572336;
    double  k0 = 0.9996;
    double  lamda0;
    double  e2;
    double  E2;
    double  n;
    double  M;
    double  T;
    double  C;
    double  A;

    phi = lat * pi / 180.0;
    lamda  = lon * pi / 180.0;
    //// ユニバーサル横メルカトル図法ではないので、ゾーンで分けない
    //// int        zone;
    //// zone = ( (int)( lon + 180 ) ) / 6  + 1;
    //// lamda0 = ( ( zone - 1 ) * 6 - 180 + 3 ) * pi / 180.0;
    lamda0  = lon0 * pi / 180.0;        // center longitude
    e2 = 2 * ( f - f * f );
    E2 = e2 / ( 1 - e2 );
    //// b = a * sqrt( 1 - e2 );
    n = a / sqrt( 1 - e2 * sin(phi) * sin(phi) );
    M = a * ( 
                ( 1 - e2/4 - 3*e2*e2/64 - 5*e2*e2*e2/256 ) * phi -
                ( 3*e2/8 + 3*e2*e2/32 + 45*e2*e2*e2/1024 ) * sin(2*phi) +
                ( 15*e2*e2/256 + 45*e2*e2*e2/1024 ) * sin(4*phi) -
                ( 35*e2*e2*e2/3072 ) * sin(6*phi)
              );
    T = tan(phi) * tan(phi);
    C = E2 * cos(phi) * cos(phi);
    A = ( lamda - lamda0 ) * cos(phi);
    *x = k0 * n * ( A + 
                    (1-T+C)*A*A*A/6 + 
                    (5 - 18*T + T*T + 72*C - 58*E2)*A*A*A*A*A*120
                  )
         ; ////+ 500000;        // ユニバーサル横メルカトル図法ではないので、経線方向にシフトさせない。X座標は負値あり
    *y = k0 * ( M + 
                n * tan(phi) * ( A*A/2 + 
                                ( 5 - T + 9*C + 4*C*C) * A*A*A*A*24 +
                                ( 61 - 58 * T + T*T + 600*C - 330*E2) * A*A*A*A*A*A*720
                                )
              );

}

/////////////////////////////////////////////////////////////////////////////
// TM座標系をマップ画像の位置に変換
POINT Projection::ConvTMToMapXy( double x, double y, double shift_x, double shift_y, double magnify )
{
    POINT pnt;

    pnt.x = (long)(( x + shift_x ) * magnify);
    pnt.y = (long)(( y * (-1) + shift_y) * magnify);

    return pnt;
}

/////////////////////////////////////////////////////////////////////////////
// メートル単位の距離をTM座標系のピクセル数に換算
double Projection::lengthTM( int length, double magnify )
{
    return  ( (double)length * magnify );
}