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

遥感矢量文件坐标系转换到WGS1984 c++版
ytkz之前写了相同的主题,但是所用的代码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 常用的那个经纬度标准,全球通用的坐标系。这代码就像个“翻译官”,把地图数据从“方言”翻译成“普通话”。
代码咋工作的?
- 先准备工具箱
#include <gdal_priv.h>
#include <ogrsf_frmts.h>
#include <ogr_spatialref.h>
这几行是“搬工具”的代码,用的 GDAL 库(地理数据处理的大神级工具)。相当于你去干活前得先把锤子、扳手啥的拿出来。
GDALAllRegister();
OGRRegisterAll();
这两句是“开工具箱”的操作,把 GDAL 和 OGR(GDAL 的矢量数据帮手)的所有功能都激活,告诉程序:“我啥都能干,别限制我!”
- 打开地图文件
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)。它就像打开一个地图文件夹,看看里面有啥。
如果打不开,就报错:“哎呀,文件找不着,干不了活!”
- 找个“保存专家”
GDALDriver* poDriver = GetGDALDriverManager()->GetDriverByName("ESRI Shapefile");
if (poDriver == nullptr) {
std::cerr << "无法加载 ESRI Shapefile 驱动" << std::endl;
GDALClose(poSrcDS);
return false;
}
这块是找个“保存专家”,专门处理 Shapefile 格式文件。GDAL 里有很多“专家”(驱动),咱们指定用 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;
}
这里是用刚才的“保存专家”新建一个文件(比如 yili_vector_wgs84_cp.shp),相当于拿出一本新笔记本,准备把翻译好的东西写进去。
创建失败就喊:“哎,新笔记本弄不出来,咋办?”
- 定好目标:WGS84
OGRSpatialReference oTargetSRS;
oTargetSRS.SetWellKnownGeogCS("WGS84");
oTargetSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
这几行是设定“翻译目标”——WGS84。
就像告诉程序:“咱们要把地图坐标全变成经纬度,而且顺序得是先经度后纬度,别弄反了!”
- 一页页翻地图,翻译过去
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。
”如果造不出来,就报错:“翻译机坏了,干不了!”
- 新建一页,抄属性
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));
}
把老地图的“表格字段”(比如道路名称、长度啥的)也抄到新页面,保证信息不丢。
- 一条条翻译坐标
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;
}
翻译完坐标后,把整条信息(坐标+属性)写到新笔记本上。写不下去就说:“哎,写不下了!”
- 收拾工具,完工
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,说不定能整出点让人眼前一亮的东西!