我有一个 C++ 程序在 Linux 上运行多年。它使用一些坐标变换,我们使用
libproj
(版本 6.3.1)来实现此目的。
有趣的是,一位客户给出了输入参数
epsg:2180
然后一切都崩溃了。
看起来对于一些 epsg 转换,输出被交换了。以下是我用来证明这一点的命令:
首先获取投影字符串:
$ projinfo epsg:2180
PROJ.4 string:
+proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs
如果我将它与如下所示的示例坐标(LatLon 格式)一起使用,我首先得到的结果是东距。 (基本上命令是
cs2cs epsg:4326 +to <projection string here> -f %.4f
)
echo "55.12578267 16.77291767" | cs2cs epsg:4326 +to +proj=tmerc +lat_0=0 +lon_0=19 +k=0.9993 +x_0=500000 +y_0=-5300000 +ellps=GRS80 +towgs84=0,0,0,0,0,0,0 +units=m +no_defs +type=crs -f %.4f
大家期待的结果:
358037.8085 | 809219.1725 | 0.0000 |
---|
但是,如果我使用简单的命令:
echo "55.12578267 16.77291767" | cs2cs epsg:4326 epsg:2180 -f %.4f
然后结果就翻转了(顾客不高兴):
809219.1725 | 358037.8085 | 0.0000 |
---|
问题并不止于此,在 C++ 中,我使用相同的库,也遇到相同的问题。问题是,如何确保东距是第一个参数?
proj_normalize_for_visualization
,因为文档建议它会在坐标上进行交换,但实际上它不会交换结果。
#include <cstdlib>
#include <cstdio>
#include <proj.h>
int main(int argc, char** argv)
{
PJ_CONTEXT* projContext_m = proj_context_create();
// hardcode epsg (in reality it is user defined)
int epsg_p = 2180;
char _buff[100];
snprintf(_buff, sizeof (_buff), "EPSG:%d", epsg_p);
PJ* projEPSG_m = proj_create_crs_to_crs(projContext_m,
"EPSG:4326",
_buff, NULL);
if (projEPSG_m != nullptr)
{
proj_normalize_for_visualization(projContext_m, projEPSG_m);
}
// fake input (in reality it is user defined)
PJ_COORD input_p;
input_p.xy.x = 55.12578267;
input_p.xy.y = 16.77291767;
PJ_COORD result = proj_trans(projEPSG_m, PJ_FWD, input_p); // input_p is the coordinate we need to transform
// we assume `result.x` is the Easting
printf("Easting: %f, Northing %f", result.xy.x, result.xy.y);
return 0;
}
输出:
东:809219.172518,北:358037.808510
我应该怎么做才能强制执行
result.x
始终是东方?
TLDR;
PROJ4 格式并不完美,使用
proj_as_projjson
获取 JSON 投影字符串并检查密钥 ["target_crs"]["coordinate_system"]["axis"][0]["direction"]
。
const char* projInfoJson = proj_as_projjson(projContext_m, projEPSG_m, NULL);
json::parse(projInfoJson)["target_crs"]["coordinate_system"]["axis"][0]["direction"] == "north";
经过几天的调试和文档阅读,很明显 PROJ4 格式并未提供所有参数。
这就是为什么
cs2cs
在仅与 EPSG 或投影字符串一起使用时产生交换结果的原因。
此外,由于历史/传统原因,一些 EPSG(包括 2180)以“东东优先”以外的方式输出结果。
看看 epsg 的网站并检查“坐标系”部分(重点是我的):
笛卡尔二维 CS。轴:北向,东向 (x,y)。方位:北、东。计量单位:米。
不幸的是,无法从 PROJ4 类型的投影字符串中辨别出此信息。还有其他格式可以提供更多信息。
所以解决方案是,获取 JSON 投影字符串,提取轴信息,并在星星对齐时使用交换。
这是执行此操作的代码:
#include <cstdlib>
#include <cstdio>
#include <proj.h>
int main(int argc, char** argv)
{
PJ_CONTEXT* projContext_m = proj_context_create();
// hardcode epsg (in reality it is user defined)
int epsg_p = 2180;
char _buff[100];
snprintf(_buff, sizeof (_buff), "EPSG:%d", epsg_p);
PJ* projEPSG_m = proj_create_crs_to_crs(projContext_m,
"EPSG:4326",
_buff, NULL);
// ----- here comes the change
bool swapNeeded = false;
if (projEPSG_m != nullptr)
{
const char* projInfoJson = proj_as_projjson(projContext_m, projEPSG_m, NULL);
nlohmann::json projInfo = nlohmann::json::parse(projInfoJson);
swapNeeded = projInfo["target_crs"]["coordinate_system"]["axis"][0]["direction"] == "north";
}
// fake input (in reality it is user defined)
PJ_COORD input_p;
input_p.xy.x = 55.12578267;
input_p.xy.y = 16.77291767;
PJ_COORD result = proj_trans(projEPSG_m, PJ_FWD, input_p); // input_p is the coordinate we need to transform
if (swapNeeded) {
std::swap(result.xy.x, result.xy.y);
}
// we assume `result.x` is the Easting
printf("Easting: %f, Northing %f", result.xy.x, result.xy.y);
return 0;
}
我使用
nlohmann::json
库进行 JSON 解析,但可能会使用其他库来读取这几个字符。