遥感矢量文件坐标系转换到WGS1984 c++版

之前写了相同的主题,但是所用的代码python。而今天写一个坐标系转换到wgs1984的c++代码的版本。首先说一下为什么要用c++写,直接用python通杀一切不好嘛?

1 为什么要用 C++ 编写坐标系转换代码?

之前我曾用 Python 实现过相同的功能——将矢量数据转换为 WGS84 坐标系。Python 凭借其丰富的库(如 GDAL 的 Python 绑定)和简洁的语法,确实在快速原型开发和小型任务中表现出色。然而,随着数据规模的增加或对性能要求的提升,Python 的局限性逐渐显现,尤其是效率问题。

Python 虽然能“通杀一切”,但它本质上是解释型语言,运行时存在较大的性能开销。特别是对于地理空间数据处理这种计算密集型任务,涉及大量坐标变换和几何操作时,Python 的单线程执行和 GIL(全局解释器锁)会导致效率瓶颈。而 C++ 作为编译型语言,可以直接生成高效的机器码,充分利用多核处理器和内存管理,显著提升性能。对于需要处理大规模矢量数据或实时性要求较高的场景,C++ 是更优的选择。

此外,使用 C++ 调用 GDAL 的原生接口,可以避免 Python 绑定带来的额外封装开销,直接操作底层数据结构,进一步提高效率。因此,今天我们将探讨如何用 C++ 实现一个将矢量数据转换为 WGS84 坐标系的功能,并分析代码实现中的关键点。

2 C++ 实现代码分析

以下是实现的核心代码。它基于 GDAL 和 OGR 库,完成了从输入矢量文件(Shapefile 格式)到 WGS84 坐标系的转换,并生成新的输出文件:

#include <gdal_priv.h>
#include <ogrsf_frmts.h>
#include <ogr_spatialref.h>
#include <string>
#include <iostream>

// 将输入矢量数据转换到 WGS84 坐标系统,并输出到新文件
bool transformVectorToWGS84(const std::string& inputFile,
                            const std::string& outputFile) {
    // 注册 GDAL 和 OGR 驱动
    GDALAllRegister();
    OGRRegisterAll();

    // 打开输入数据集(矢量数据)
    GDALDataset* poSrcDS = (GDALDataset*)GDALOpenEx(
            inputFile.c_str(),
            GDAL_OF_VECTOR,
            nullptr,
            nullptr,
            nullptr);
    if (poSrcDS == nullptr) {
        std::cerr << "无法打开输入文件: " << inputFile << std::endl;
        return false;
    }

    // 获取 ESRI Shapefile 驱动
    GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
    if (poDriver == nullptr) {
        std::cerr << "无法加载 ESRI Shapefile 驱动" << std::endl;
        GDALClose(poSrcDS);
        return false;
    }

    // 创建输出数据集(新 Shapefile)
    GDALDataset* poDstDS = poDriver->Create(
            outputFile.c_str(),
            0, 0, 0,
            GDT_Unknown,
            nullptr);
    if (poDstDS == nullptr) {
        std::cerr << "无法创建输出文件: " << outputFile << std::endl;
        GDALClose(poSrcDS);
        return false;
    }

    // 定义目标空间参考(WGS84)
    OGRSpatialReference oTargetSRS;
    oTargetSRS.SetWellKnownGeogCS("WGS84");
    oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); // 强制经度-纬度顺序

    // 遍历输入数据集中的每个图层
    for (int iLayer = 0; iLayer < poSrcDS->GetLayerCount(); iLayer++) {
        OGRLayer* poSrcLayer = poSrcDS->GetLayer(iLayer);
        if (poSrcLayer == nullptr) {
            std::cerr << "11" << std::endl;
            continue;
        }

        // 获取输入图层的空间参考
        const OGRSpatialReference* poSrcSRS = poSrcLayer->GetSpatialRef();
        if (poSrcSRS == nullptr) {
            std::cerr << "图层中没有空间参考信息。" << std::endl;
            continue;
        }

        // 创建坐标转换对象:从输入空间参考转换到 WGS84
        OGRCoordinateTransformation* poCT = OGRCreateCoordinateTransformation(
                const_cast<OGRSpatialReference*>(poSrcSRS), &oTargetSRS);
        if (poCT == nullptr) {
            std::cerr << "无法创建坐标转换" << std::endl;
            continue;
        }

        // 创建输出图层,使用输入图层的名称、几何类型,并指定目标空间参考
        OGRLayer* poDstLayer = poDstDS->CreateLayer(
                poSrcLayer->GetName(),
                &oTargetSRS,
                poSrcLayer->GetGeomType(),
                nullptr);
        if (poDstLayer == nullptr) {
            std::cerr << "111" << std::endl;
            OGRCoordinateTransformation::DestroyCT(poCT);
            continue;
        }

        // 复制字段定义
        OGRFeatureDefn* poFDefn = poSrcLayer->GetLayerDefn();
        for (int i = 0; i < poFDefn->GetFieldCount(); i++) {
            poDstLayer->CreateField(poFDefn->GetFieldDefn(i));
        }

        // 遍历输入图层中的每个要素,并进行坐标转换
        OGRFeature* poSrcFeature = nullptr;
        poSrcLayer->ResetReading();
        while ((poSrcFeature = poSrcLayer->GetNextFeature()) != nullptr) {
            OGRGeometry* poGeometry = poSrcFeature->GetGeometryRef();
            if (poGeometry != nullptr) {
                // 执行几何对象的坐标转换
                if (poGeometry->transform(poCT) != OGRERR_NONE) {
                    std::cerr << "11" << std::endl;
                }
            }

            // 创建新的要素,复制属性信息,并添加到输出图层
            OGRFeature* poDstFeature = new OGRFeature(poDstLayer->GetLayerDefn());
            poDstFeature->SetFrom(poSrcFeature);
            if (poDstLayer->CreateFeature(poDstFeature) != OGRERR_NONE) {
                std::cerr << "1" << std::endl;
            }

//            OGRFeature::DestroyFeature(poDstFeature);
//            OGRFeature::DestroyFeature(poSrcFeature);
        }

        // 销毁坐标转换对象
        OGRCoordinateTransformation::DestroyCT(poCT);
    }

    // 关闭数据集,释放资源
    GDALClose(poSrcDS);
    GDALClose(poDstDS);

    return true;
}

// 示例:转换指定输入 Shapefile 到 WGS84 并保存为新的 Shapefile
int main() {
    std::string inputPath = "F:\\vector\\yili_vector.shp";
    std::string outputPath = "F:\\vector\\yili_vector_wgs84_cp.shp";

    if (transformVectorToWGS84(inputPath, outputPath)) {
        std::cout << "11" << std::endl;
    } else {
        std::cerr << "1111" << std::endl;
    }

    return 0;
}

这段代码干啥的?

简单说,这段 C++ 代码是用来把一个地理数据文件(比如 Shapefile 格式的地图数据)从它原来的坐标系转成 WGS84 坐标系,然后保存成一个新文件。

WGS84 是啥?就是咱们 GPS 常用的那个经纬度标准,全球通用的坐标系。这代码就像个“翻译官”,把地图数据从“方言”翻译成“普通话”。

代码咋工作的?

  1. 先准备工具箱
#include <gdal_priv.h>
#include <ogrsf_frmts.h>
#include <ogr_spatialref.h>

这几行是“搬工具”的代码,用的 GDAL 库(地理数据处理的大神级工具)。相当于你去干活前得先把锤子、扳手啥的拿出来。

GDALAllRegister();
OGRRegisterAll();

这两句是“开工具箱”的操作,把 GDAL 和 OGR(GDAL 的矢量数据帮手)的所有功能都激活,告诉程序:“我啥都能干,别限制我!”

  1. 打开地图文件
GDALDataset* poSrcDS = (GDALDataset*)GDALOpenEx(inputFile.c_str(), GDAL_OF_VECTOR, nullptr, nullptr, nullptr);
if (poSrcDS == nullptr) {
    std::cerr << "无法打开输入文件: " << inputFile << std::endl;
    return false;
}

这里是用 GDALOpenEx 打开你给的文件(比如 yili_vector.shp)。它就像打开一个地图文件夹,看看里面有啥。

如果打不开,就报错:“哎呀,文件找不着,干不了活!”

  1. 找个“保存专家”
GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
if (poDriver == nullptr) {
    std::cerr << "无法加载 ESRI Shapefile 驱动" << std::endl;
    GDALClose(poSrcDS);
    return false;
}

这块是找个“保存专家”,专门处理 Shapefile 格式文件。GDAL 里有很多“专家”(驱动),咱们指定用 Shapefile 这个。

如果没找到,就说:“没这家伙,我没法保存!”

  1. 创建新文件当“笔记本”
GDALDataset* poDstDS = poDriver->Create(outputFile.c_str(), 0, 0, 0, GDT_Unknown, nullptr);
if (poDstDS == nullptr) {
    std::cerr << "无法创建输出文件: " << outputFile << std::endl;
    GDALClose(poSrcDS);
    return false;
}

这里是用刚才的“保存专家”新建一个文件(比如 yili_vector_wgs84_cp.shp),相当于拿出一本新笔记本,准备把翻译好的东西写进去。

创建失败就喊:“哎,新笔记本弄不出来,咋办?”

  1. 定好目标:WGS84
OGRSpatialReference oTargetSRS;
oTargetSRS.SetWellKnownGeogCS("WGS84");
oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);

这几行是设定“翻译目标”——WGS84。

就像告诉程序:“咱们要把地图坐标全变成经纬度,而且顺序得是先经度后纬度,别弄反了!”

  1. 一页页翻地图,翻译过去
for (int iLayer = 0; iLayer < poSrcDS->GetLayerCount(); iLayer++) {
    OGRLayer* poSrcLayer = poSrcDS->GetLayer(iLayer);

地图文件可能有好几页(图层),比如道路、河流啥的。

这里是循环翻页,一页页处理。

const OGRSpatialReference* poSrcSRS = poSrcLayer->GetSpatialRef();
if (poSrcSRS == nullptr) {
    std::cerr << "图层中没有空间参考信息。" << std::endl;
    continue;
}

每页得先看看它原来用的是啥坐标系(空间参考)。

如果没写坐标系,就跳过,说:“这页啥也不知道,翻译不了!”

OGRCoordinateTransformation* poCT = OGRCreateCoordinateTransformation(
    const_cast<OGRSpatialReference*>(poSrcSRS), &oTargetSRS);

这里是造个“翻译机”,告诉它:“从原来的坐标系翻成 WGS84。

”如果造不出来,就报错:“翻译机坏了,干不了!”

  1. 新建一页,抄属性
OGRLayer* poDstLayer = poDstDS->CreateLayer(poSrcLayer->GetName(), &oTargetSRS, poSrcLayer->GetGeomType(), nullptr);

在新笔记本上新建一页,名字和原来一样,坐标系设成 WGS84,形状类型(点、线、面)也照抄。

OGRFeatureDefn* poFDefn = poSrcLayer->GetLayerDefn();
for (int i = 0; i < poFDefn->GetFieldCount(); i++) {
    poDstLayer->CreateField(poFDefn->GetFieldDefn(i));
}

把老地图的“表格字段”(比如道路名称、长度啥的)也抄到新页面,保证信息不丢。

  1. 一条条翻译坐标
while ((poSrcFeature = poSrcLayer->GetNextFeature()) != nullptr) {
    OGRGeometry* poGeometry = poSrcFeature->GetGeometryRef();
    if (poGeometry != nullptr) {
        if (poGeometry->transform(poCT) != OGRERR_NONE) {
            std::cerr << "error" << std::endl;
        }
    }

这里是核心干活的地方!挨个读老地图上的东西(要素),把它的坐标拿出来,用“翻译机”转成 WGS84。

如果翻译出错,就喊:“哎,这条翻砸了!”

OGRFeature* poDstFeature = new OGRFeature(poDstLayer->GetLayerDefn());
poDstFeature->SetFrom(poSrcFeature);
if (poDstLayer->CreateFeature(poDstFeature) != OGRERR_NONE) {
    std::cerr << "error" << std::endl;
}

翻译完坐标后,把整条信息(坐标+属性)写到新笔记本上。写不下去就说:“哎,写不下了!”

  1. 收拾工具,完工
OGRCoordinateTransformation::DestroyCT(poCT);
GDALClose(poSrcDS);
GDALClose(poDstDS);

干完活,把“翻译机”扔了,关掉老地图和新笔记本,收拾干净走人。


主函数:喊一嗓子试试

int main() {
    std::string inputPath = "F:\\vector\\yili_vector.shp";
    std::string outputPath = "F:\\vector\\yili_vector_wgs84_cp.shp";
    if (transformVectorToWGS84(inputPath, outputPath)) {
        std::cout << "1" << std::endl;
    } else {
        std::cerr << "2" << std::endl;
    }
    return 0;
}

最后这个 main,就是喊一嗓子:“拿这个文件,给我转成 WGS84!”转成功了就说“1”,失败了就喊“2”。

小白看完咋办?

咋跑起来?

  • 你得先装 GDAL 库(网上有教程,搜“GDAL Windows 安装”啥的)。
  • 把代码拷到 C++ 编译器(比如 Visual Studio),改下文件路径,点“运行”就行。

这代码就像个老实干活的工人,能用,但还有点糙。想让它更牛?加点多线程啥的,跑得飞快!小白别怕,照着玩一玩就明白了!

小结

通过这次从 Python 跳到 C++ 的尝试,我算是真切感受到不同语言在不同场景下的厉害之处。Python 写起来快,验证想法特别顺手,但一碰到性能要求高的时候就有点吃力。而 C++ 虽然上手麻烦点,可跑起来真不是盖的,尤其在这种性能敏感的任务上。这段代码不仅搞定了坐标系转换,还给后面优化和扩展留了余地。接下来,我想再折腾下多线程和批处理技术,看看能不能在大规模地理数据处理上再猛一把。

如果你也有这方面的需求,不妨试试 C++ 搭上 GDAL,说不定能整出点让人眼前一亮的东西!