PROJ将WGS84转UTM坐标


Reference:

  1. PROJ
  2. 地图坐标系大全:常用地图坐标系详解与转换指南
  3. ofxGeo: A set of utilities and classes for common geographic tasks

1. PROJ 介绍

PROJ 是一个通用的坐标转换软件,它将地理空间坐标从一个坐标参考系统(coordinate reference system, CRS)转换到另一个坐标参考系统。这包括地图投影以及大地测量转换。

2. 地图坐标系的基本概念和原理

地图坐标系是用于描述地图上位置的数学模型。它可以用来表示地球表面上的任意一个点,使得这个点的位置可以在地图上精确定位。不同的地图坐标系采用不同的基准面和投影方式,因此会有不同的坐标系参数,不同的坐标系之间也需要进行坐标转换。在地图制图、导航、GIS等领域中,地图坐标系是一个非常重要的概念。

地球是一个近似于椭球体的三维物体。由于地球的形状和尺寸非常复杂,为了方便测量和描述地球上的位置,我们需要采用一些数学模型来描述地球表面。通常情况下,我们会采用基准面和投影方式来建立地图坐标系。

基准面是一个参考面,通常用来定义地球的形状和尺寸。地球的形状可以用椭球体或者大地水准面来描述。椭球体是一种近似于地球形状的数学模型,它由长半轴和短半轴两个参数来描述。大地水准面则是以重力为基础建立的地球表面上的平均海平面,用来描述地球的真实形状。

投影方式是一种将三维地球表面投影到二维平面上的方法。由于地球是一个三维物体,所以我们需要将地球表面投影到一个平面上来制作地图。不同的投影方式会影响地图上不同地区的形状和面积。

3. 常见的地图坐标系类型

3.1 地球经纬度坐标系

地球经纬度坐标系是一种球面坐标系,通常用于描述地球表面上的位置。它将地球分为两个半球,分别为东半球和西半球,以及南半球和北半球,然后通过经度和纬度来确定在球面上的位置。

经度是指在地球上从南极到北极的虚拟线,也称为子午线。经度的取值范围是-180到180度,其中0度经线是本初子午线,位于英国伦敦的皇家格林威治天文台内。东经表示为正数,西经表示为负数。

纬度是指从地球中心到地球表面的一条线,垂直于经度线。纬度的取值范围是-90到90度,其中赤道是0度纬线,南纬表示为负数,北纬表示为正数。

地球经纬度坐标系的优点是易于理解和计算,但由于地球是一个球体,所以在地图制图和空间分析中使用时需要进行坐标变换。

3.2 地心坐标系(如WGS-84坐标系)

在这里插入图片描述
地心坐标系是以地球的质心作为坐标原点,以地球自转轴为Z轴的坐标系,通常用于描述地球的物理特性、导航定位等应用。其中最为广泛使用的地心坐标系之一是WGS-84坐标系,它以国际地球参考系(ITRF)为基准,通过数学模型和测量数据来描述地球的形状和旋转。

WGS-84坐标系是一种三维坐标系,以地球的质心为原点,以经线和纬线为基准,通过大量的测量数据来确定地球的形状、大小和自转角速度等参数,从而实现对地球位置的准确描述。其坐标轴定义如下:

  • X轴:指向经度为0°,纬度为0°的点,即地球的交点。
  • Y轴:指向经度为90°E,纬度为0°的点,即赤道上的某一点。
  • Z轴:与地球自转轴重合,指向北极点。

在WGS-84坐标系中,地球的形状被抽象为一个椭球体,其长半轴为6378137米,短半轴为6356752.3142米,离心率为0.0818191910428。通过将经纬度坐标转换为WGS-84坐标系中的XYZ坐标,可以实现地球上的位置计算和导航定位等应用。

3.3 平面直角坐标系(如UTM坐标系)

平面直角坐标系(Planar Coordinate Systems)是一种基于平面直角坐标系的坐标系统,通常用于相对较小的地理区域,例如城市、州、国家等范围内。在平面直角坐标系中,地球表面被划分为网格,每个位置都有唯一的坐标值。

UTM坐标系(Universal Transverse Mercator)是平面直角坐标系中最常用的坐标系统之一。UTM坐标系把地球表面划分成60个纵向带,每个带对应一个投影系统。其中纵向带覆盖从经度0度到经度360度,横向带则依据赤道线和两极线的位置进行划分。每个带内的坐标系统采用横向墨卡托投影或是纵向墨卡托投影,将地球表面投影到平面上,并使用假东、假北坐标表示位置。可见Universal Transverse Mercator (UTM)
在这里插入图片描述

UTM zones.

相对于经纬度坐标系,平面直角坐标系在短距离内能够提供更高的精度,通常用于测量、地图绘制等领域。但是,由于平面直角坐标系在较长距离内会出现较大的投影误差,因此并不适用于大范围的测量和导航等应用。

3.3.1 坐标系的定义和用途

平面直角坐标系是一种直角坐标系,用于在地图上将地球表面的位置表示为平面上的二维坐标,它是一种局部坐标系,适用于较小区域的地图制图和定位。

3.3.2 坐标系的投影方式

平面直角坐标系采用了多种不同的投影方式,常见的有横轴墨卡托投影、高斯-克吕格投影、UTM投影等。不同的投影方式适用于不同的地区和地形特征。

3.3.3 坐标系的基准面

平面直角坐标系的基准面可以是椭球面或平面,一般来说采用的是具有特定参数的椭球面作为基准面,如国际1924年椭球、WGS-84椭球等。

3.3.4 坐标系的坐标表示和转换方法

平面直角坐标系的坐标表示一般采用x,y两个坐标值来表示,也可以用距离和方位角等方式表示。坐标转换方法一般采用正反算法,通过将地球表面的经纬度坐标转换为平面坐标,或者将平面坐标转换为经纬度坐标。

3.3.5 坐标系的优缺点和适用范围

平面直角坐标系的优点是计算简单,精度高,易于处理,适用于小范围地图制作和定位;缺点是不适用于全球范围的制图,存在地区性差异,如有面积变形、方向畸变等问题。平面直角坐标系适用于城市地图、建筑设计、导航定位等领域。

3.4 地方坐标系(如高斯-克吕格坐标系)

地方坐标系(Local Coordinate System)是在地球表面上某个点处定义的坐标系,其坐标轴一般是平面直角坐标系,由局部大地基准确定。它与全球坐标系的转换需要考虑地球的椭球形状和大地基准的差异。

其中,高斯-克吕格坐标系(Gauss-Krüger Coordinate System),也叫高斯投影坐标系,是常见的地方坐标系之一。在高斯-克吕格坐标系中,地球被分成若干个带状区域,每个区域内可以通过一个平面直角坐标系来表示坐标。在中国,高斯-克吕格投影一般采用六度带,每个带宽度为 6 度,以中央经线为中心,向东西两边各三度。

由于高斯-克吕格坐标系是地方坐标系,因此其坐标原点和带宽度的选取是与实际地理情况相关的。在中国,高斯-克吕格坐标系一般采用 1954 年版北京坐标系(BJS54)作为大地基准,因此需要将 WGS-84 坐标系下的经纬度坐标先转换到 BJS54 坐标系下,然后再将其转换到高斯-克吕格坐标系下。

与经纬度坐标系相比,高斯-克吕格坐标系具有以下优点:

  • 坐标值易于处理和计算,且可表示为实数形式;
  • 在较小的区域内,投影误差较小,精度较高;
  • 各地图投影的形状、大小、比例等参数是已知的,具有较好的相对精度和可比性。

不过,由于高斯-克吕格坐标系是局部的,其不适用于跨越多个投影带的情况。此外,在大范围地图上使用高斯-克吕格投影会引入较大的投影误差,因此在较大区域内使用 WGS-84 坐标系更为合适。

4. 坐标系之间的转换方法

4.1 经纬度与平面坐标系之间的转换

经纬度与平面坐标系之间的转换需要进行投影转换,通常采用的方法是先将经纬度坐标转换为笛卡尔坐标系(三维空间直角坐标系),然后再进行投影转换得到平面坐标系。

具体来说,经纬度转平面坐标系通常需要进行以下步骤

  1. 将经纬度转换为地心空间直角坐标系,即WGS-84坐标系或其它地心坐标系。
  2. 将地心空间直角坐标系转换为局部平面直角坐标系,即UTM坐标系或其它局部坐标系。
  3. 根据地图投影的不同,将局部平面直角坐标系进行投影转换得到平面坐标系,如高斯-克吕格投影、墨卡托投影等。

平面坐标系转经纬度也需要进行反向的投影转换,具体步骤与上述相反,通常需要先将平面坐标系转换为局部平面直角坐标系,然后再转换为地心空间直角坐标系,最后转换为经纬度坐标。

以下是一个简单的C代码示例,演示如何使用Proj库将经纬度坐标转换为UTM平面坐标:

#include <stdio.h>
#include "proj_api.h"
 
int main() {
    
    
    projPJ pj_utm, pj_latlong;
    double x, y;
    double lat = 40.748817, lon = -73.985428; // 纽约市时代广场的经纬度
 
    // 定义投影方式
    pj_latlong = pj_init_plus("+proj=latlong +datum=WGS84");
    pj_utm = pj_init_plus("+proj=utm +zone=18 +datum=WGS84");
 
    // 将经纬度转换为UTM坐标
    pj_transform(pj_latlong, pj_utm, 1, 1, &lon, &lat, NULL);
    printf("UTM坐标为: %.3f, %.3f\n", x, y);
 
    // 释放投影对象
    pj_free(pj_latlong);
    pj_free(pj_utm);
 
    return 0;
}

这个示例使用 Proj 库中的 proj_init_plus() 函数定义了两个投影方式,一个是WGS84椭球体下的经纬度坐标系,另一个是WGS84椭球体下的UTM坐标系。然后,它使用 pj_transform() 函数将经纬度坐标(lon, lat)转换为UTM坐标(x, y)。最后,使用 pj_free() 函数释放投影对象。

请注意,这只是一个简单的示例。在实际应用中,您需要考虑更多因素,例如所使用的椭球体、投影带、坐标单位等。

4.2 PROJ 参数说明

Parameters Descriptions
+zone=<value> 选择要使用的UTM区域,取值范围为1-60
+south 在南半球使用UTM时添加此标志
+approx 对UTM使用更快,但精度有降低的算法
+ellps=<value> 内置椭球定义的名称,默认为“GRS80”。如果size和shape被指定为ellps=xxx,后面的shape和size参数将被作为内置椭球定义的修饰符考虑。(见proj -le)
+datum=<value> 基准面名(见proj -ld)
+proj=<value> 投影名(见proj -l)

关于 ellps 与 datum 的区别:
基准面指定参数以映射椭球体上的坐标。其中一个参数是椭球中心的原点移位。

  1. Difference between datum and ellipsoid for geodetic coordinates?

4.3 不使用PROJ的转换代码

这里参考的是 ofxGeo 工程:

/* -*- mode: C++ -*-
 *
 *  Conversions between Latitude/Longitude and UTM
 *              (Universal Transverse Mercator) coordinates.
 *
 *  License: Modified BSD Software License Agreement
 *
 *  $Id$
 */

#ifndef _UTM_H
#define _UTM_H

/**  @file
 @brief Universal Transverse Mercator transforms.
 Functions to convert (spherical) latitude and longitude to and
 from (Euclidean) UTM coordinates.
 @author Chuck Gantz- [email protected]
 */

#include <cmath>
#include <stdio.h>
#include <stdlib.h>
#include "ofMathConstants.h"


namespace UTM
{
    
    
    // Grid granularity for rounding UTM coordinates to generate MapXY.
    const double grid_size = 100000.0;    ///< 100 km grid

// WGS84 Parameters
#define WGS84_A		6378137.0		///< major axis
#define WGS84_B		6356752.31424518	///< minor axis
#define WGS84_F		0.0033528107		///< ellipsoid flattening
#define WGS84_E		0.0818191908		///< first eccentricity
#define WGS84_EP	0.0820944379		///< second eccentricity

    // UTM Parameters
#define UTM_K0		0.9996			///< scale factor
#define UTM_FE		500000.0		///< false easting
#define UTM_FN_N	0.0           ///< false northing, northern hemisphere
#define UTM_FN_S	10000000.0    ///< false northing, southern hemisphere
#define UTM_E2		(WGS84_E*WGS84_E)	///< e^2
#define UTM_E4		(UTM_E2*UTM_E2)		///< e^4
#define UTM_E6		(UTM_E4*UTM_E2)		///< e^6
#define UTM_EP2		(UTM_E2/(1-UTM_E2))	///< e'^2

    /**
     * Determine the correct UTM letter designator for the
     * given latitude
     *
     * @returns 'Z' if latitude is outside the UTM limits of 84N to 80S
     *
     * Written by Chuck Gantz- [email protected]
     */
    static inline char UTMLetterDesignator(double Lat)
    {
    
    
        char LetterDesignator;

        if     ((84 >= Lat) && (Lat >= 72))  LetterDesignator = 'X';
        else if ((72 > Lat) && (Lat >= 64))  LetterDesignator = 'W';
        else if ((64 > Lat) && (Lat >= 56))  LetterDesignator = 'V';
        else if ((56 > Lat) && (Lat >= 48))  LetterDesignator = 'U';
        else if ((48 > Lat) && (Lat >= 40))  LetterDesignator = 'T';
        else if ((40 > Lat) && (Lat >= 32))  LetterDesignator = 'S';
        else if ((32 > Lat) && (Lat >= 24))  LetterDesignator = 'R';
        else if ((24 > Lat) && (Lat >= 16))  LetterDesignator = 'Q';
        else if ((16 > Lat) && (Lat >= 8))   LetterDesignator = 'P';
        else if (( 8 > Lat) && (Lat >= 0))   LetterDesignator = 'N';
        else if (( 0 > Lat) && (Lat >= -8))  LetterDesignator = 'M';
        else if ((-8 > Lat) && (Lat >= -16)) LetterDesignator = 'L';
        else if((-16 > Lat) && (Lat >= -24)) LetterDesignator = 'K';
        else if((-24 > Lat) && (Lat >= -32)) LetterDesignator = 'J';
        else if((-32 > Lat) && (Lat >= -40)) LetterDesignator = 'H';
        else if((-40 > Lat) && (Lat >= -48)) LetterDesignator = 'G';
        else if((-48 > Lat) && (Lat >= -56)) LetterDesignator = 'F';
        else if((-56 > Lat) && (Lat >= -64)) LetterDesignator = 'E';
        else if((-64 > Lat) && (Lat >= -72)) LetterDesignator = 'D';
        else if((-72 > Lat) && (Lat >= -80)) LetterDesignator = 'C';
        // 'Z' is an error flag, the Latitude is outside the UTM limits
        else LetterDesignator = 'Z';
        return LetterDesignator;
    }

    /**
     * Convert lat/long to UTM coords.  Equations from USGS Bulletin 1532
     *
     * East Longitudes are positive, West longitudes are negative.
     * North latitudes are positive, South latitudes are negative
     * Lat and Long are in fractional degrees
     *
     * Written by Chuck Gantz- [email protected]
     */
    static inline void LLtoUTM(const double Lat, const double Long,
                               double &UTMNorthing, double &UTMEasting,
                               char* UTMZone)
    {
    
    
        double a = WGS84_A;
        double eccSquared = UTM_E2;
        double k0 = UTM_K0;

        double LongOrigin;
        double eccPrimeSquared;
        double N, T, C, A, M;

        //Make sure the longitude is between -180.00 .. 179.9
        double LongTemp = (Long+180)-int((Long+180)/360)*360-180;

        double LatRad = Lat*DEG_TO_RAD;
        double LongRad = LongTemp*DEG_TO_RAD;
        double LongOriginRad;
        int    ZoneNumber;

        ZoneNumber = int((LongTemp + 180)/6) + 1;

        if( Lat >= 56.0 && Lat < 64.0 && LongTemp >= 3.0 && LongTemp < 12.0 )
            ZoneNumber = 32;

        // Special zones for Svalbard
        if( Lat >= 72.0 && Lat < 84.0 )
        {
    
    
            if(      LongTemp >= 0.0  && LongTemp <  9.0 ) ZoneNumber = 31;
            else if( LongTemp >= 9.0  && LongTemp < 21.0 ) ZoneNumber = 33;
            else if( LongTemp >= 21.0 && LongTemp < 33.0 ) ZoneNumber = 35;
            else if( LongTemp >= 33.0 && LongTemp < 42.0 ) ZoneNumber = 37;
        }
        // +3 puts origin in middle of zone
        LongOrigin = (ZoneNumber - 1)*6 - 180 + 3;
        LongOriginRad = LongOrigin * DEG_TO_RAD;

        //compute the UTM Zone from the latitude and longitude
        sprintf(UTMZone, "%d%c", ZoneNumber, UTMLetterDesignator(Lat));

        eccPrimeSquared = (eccSquared)/(1-eccSquared);

        N = a/sqrt(1-eccSquared*sin(LatRad)*sin(LatRad));
        T = tan(LatRad)*tan(LatRad);
        C = eccPrimeSquared*cos(LatRad)*cos(LatRad);
        A = cos(LatRad)*(LongRad-LongOriginRad);

        M = a*((1 - eccSquared/4 - 3*eccSquared*eccSquared/64
                - 5*eccSquared*eccSquared*eccSquared/256) * LatRad
               - (3*eccSquared/8 + 3*eccSquared*eccSquared/32
                  + 45*eccSquared*eccSquared*eccSquared/1024)*sin(2*LatRad)
               + (15*eccSquared*eccSquared/256
                  + 45*eccSquared*eccSquared*eccSquared/1024)*sin(4*LatRad)
               - (35*eccSquared*eccSquared*eccSquared/3072)*sin(6*LatRad));

        UTMEasting = (double)
        (k0*N*(A+(1-T+C)*A*A*A/6
               + (5-18*T+T*T+72*C-58*eccPrimeSquared)*A*A*A*A*A/120)
         + 500000.0);

        UTMNorthing = (double)
        (k0*(M+N*tan(LatRad)
             *(A*A/2+(5-T+9*C+4*C*C)*A*A*A*A/24
               + (61-58*T+T*T+600*C-330*eccPrimeSquared)*A*A*A*A*A*A/720)));

        if(Lat < 0)
        {
    
    
            //10000000 meter offset for southern hemisphere
            UTMNorthing += 10000000.0;
        }
    }

    /**
     * Converts UTM coords to lat/long.  Equations from USGS Bulletin 1532
     *
     * East Longitudes are positive, West longitudes are negative.
     * North latitudes are positive, South latitudes are negative
     * Lat and Long are in fractional degrees.
     *
     * Written by Chuck Gantz- [email protected]
     */
    static inline void UTMtoLL(const double UTMNorthing, const double UTMEasting,
                               const char* UTMZone, double& Lat,  double& Long )
    {
    
    
        double k0 = UTM_K0;
        double a = WGS84_A;
        double eccSquared = UTM_E2;
        double eccPrimeSquared;
        double e1 = (1-sqrt(1-eccSquared))/(1+sqrt(1-eccSquared));
        double N1, T1, C1, R1, D, M;
        double LongOrigin;
        double mu, phi1Rad;
        double x, y;
        int ZoneNumber;
        char* ZoneLetter;

        x = UTMEasting - 500000.0; //remove 500,000 meter offset for longitude
        y = UTMNorthing;

        ZoneNumber = strtoul(UTMZone, &ZoneLetter, 10);
        if((*ZoneLetter - 'N') < 0)
        {
    
    
            //remove 10,000,000 meter offset used for southern hemisphere
            y -= 10000000.0;
        }

        //+3 puts origin in middle of zone
        LongOrigin = (ZoneNumber - 1)*6 - 180 + 3;
        eccPrimeSquared = (eccSquared)/(1-eccSquared);
        
        M = y / k0;
        mu = M/(a*(1-eccSquared/4-3*eccSquared*eccSquared/64
                   -5*eccSquared*eccSquared*eccSquared/256));
        
        phi1Rad = mu + ((3*e1/2-27*e1*e1*e1/32)*sin(2*mu) 
                        + (21*e1*e1/16-55*e1*e1*e1*e1/32)*sin(4*mu)
                        + (151*e1*e1*e1/96)*sin(6*mu));
        
        N1 = a/sqrt(1-eccSquared*sin(phi1Rad)*sin(phi1Rad));
        T1 = tan(phi1Rad)*tan(phi1Rad);
        C1 = eccPrimeSquared*cos(phi1Rad)*cos(phi1Rad);
        R1 = a*(1-eccSquared)/pow(1-eccSquared*sin(phi1Rad)*sin(phi1Rad), 1.5);
        D = x/(N1*k0);
        
        Lat = phi1Rad - ((N1*tan(phi1Rad)/R1)
                         *(D*D/2
                           -(5+3*T1+10*C1-4*C1*C1-9*eccPrimeSquared)*D*D*D*D/24
                           +(61+90*T1+298*C1+45*T1*T1-252*eccPrimeSquared
                             -3*C1*C1)*D*D*D*D*D*D/720));
        
        Lat = Lat * RAD_TO_DEG;
        
        Long = ((D-(1+2*T1+C1)*D*D*D/6
                 +(5-2*C1+28*T1-3*C1*C1+8*eccPrimeSquared+24*T1*T1)
                 *D*D*D*D*D/120)
                / cos(phi1Rad));
        Long = LongOrigin + Long * RAD_TO_DEG;
        
    }
} // end namespace UTM

#endif // _UTM_H

猜你喜欢

转载自blog.csdn.net/qq_28087491/article/details/129634708