一、实现原理
在小范围场景下,可以假设GPS经纬度值都在一个平面上,地理正东方向为经度正方向,正北方向为纬度正方向,正上方向为高度正方向,至此经纬高度坐标系已经建立。而我们要做的是将其转换到一个以米为度量单位的平面坐标系下,平面坐标系X轴指向地理正东方向、Y轴指向地理正北方向、Z轴指向正上方向,具体如下图所示:
在确定好各坐标系之后,计算出X轴方向单位米对应经度的变化量gridLon、Y轴方向单位米对应纬度变化量gridLat以及Z轴方向单位米对应高度变化量gridHei。接着选择一个起始点作为经纬高度坐标系原点(,
,
)和XYZ平面坐标系原点(
,
,
)。则各点GPS经纬高度值(
,
,
)转换为平面XYZ坐标值(
,
,
)关系式如下所示:
如此,便可以实现小场景下GPS经纬高度值转换为平面XYZ坐标值。
二、代码实现
测试环境:Ubuntu18.04系统 + Ros-Melodic版本 + C++编程;
1、gps_to_xyz.cpp
#include<ros/ros.h>
#include <nav_msgs/Path.h>
#include <sensor_msgs/NavSatFix.h>
#include <geometry_msgs/PoseStamped.h>
using namespace std;
//角度制转弧度制
double rad(double d)
{
return d * 3.14159265 / 180.0;
}
// ROS订阅者和发布者
ros::Subscriber gps_data_sub;
ros::Publisher path_pub;
// 全局变量
string gps_sub_topic = "";
string output_frame_name ="";
double z_rotate_value = 0.0;
double origin_latitude_value = 0.0;
double origin_longitude_value = 0.0;
double origin_altitude_value = 0.0;
double latitude_resolution = 0.0;
double longitude_resolution = 0.0;
double altitude_resolution = 0.0;
bool init_flag = true;
nav_msgs::Path path;
void gps_to_xyz(const sensor_msgs::NavSatFix::ConstPtr& gps_msg)
{
if (init_flag == true)
{
origin_latitude_value = gps_msg->latitude;
origin_longitude_value = gps_msg->longitude;
origin_altitude_value = gps_msg->altitude;
init_flag = false;
}
else
{
double gps_lat = gps_msg->latitude;
double gps_lon = gps_msg->longitude;
double gps_hei = gps_msg->altitude;
double gps_xx = (gps_lon - origin_longitude_value) / longitude_resolution;
double gps_yy = (gps_lat - origin_latitude_value) / latitude_resolution;
double gps_zz = (gps_hei - origin_altitude_value) / altitude_resolution;
double gps_x = cos(rad(z_rotate_value)) * gps_xx + (-sin(rad(z_rotate_value))) * gps_yy;
double gps_y = sin(rad(z_rotate_value)) * gps_xx + cos(rad(z_rotate_value)) * gps_yy;
double gps_z = gps_zz;
if (path_pub.getNumSubscribers() > 0)
{
geometry_msgs::PoseStamped this_pose_stamped;
this_pose_stamped.header.frame_id = output_frame_name;
this_pose_stamped.pose.position.x = gps_x;
this_pose_stamped.pose.position.y = gps_y;
this_pose_stamped.pose.position.z = gps_z;
this_pose_stamped.pose.orientation.x = 0.0;
this_pose_stamped.pose.orientation.y = 0.0;
this_pose_stamped.pose.orientation.z = 0.0;
this_pose_stamped.pose.orientation.w = 1.0;
path.poses.push_back(this_pose_stamped);
path.header.frame_id = output_frame_name;
}
path_pub.publish(path);
}
}
int main(int argc,char **argv)
{
ros::init(argc,argv,"gps_deal");
ros::NodeHandle nh;
nh.param<string>("gps_sub_topic", gps_sub_topic, "/gps");
nh.param<string>("output_frame_name", output_frame_name, "map");
nh.param<bool>("auto_get_origin_gps", init_flag, true);
nh.param<double>("z_rotate_value", z_rotate_value, 1.0);
nh.param<double>("origin_latitude_value", origin_latitude_value, 1.0);
nh.param<double>("origin_longitude_value", origin_longitude_value, 1.0);
nh.param<double>("origin_altitude_value", origin_altitude_value, 1.0);
nh.param<double>("latitude_resolution", latitude_resolution, 1.0);
nh.param<double>("longitude_resolution", longitude_resolution, 1.0);
nh.param<double>("altitude_resolution", altitude_resolution, 1.0);
gps_data_sub = nh.subscribe<sensor_msgs::NavSatFix>(gps_sub_topic, 1000, gps_to_xyz);
path_pub = nh.advertise<nav_msgs::Path>("/gpsTrack",1, true);
ros::spin();
return 0;
}
2、gps_to_xyz.launch
<launch>
<param name="gps_sub_topic" value = "/gps" /> // 订阅GPS数据话题名称
<param name="output_frame_name" value = "map" /> // 输出frame名称
<param name="auto_get_origin_gps" value = "true" /> // 是否自动获取起始点(数据集第一个GPS数据)经纬高度信息
<param name="z_rotate_value" value = "0.0" /> // 对整个轨迹进行旋转操作
<param name="origin_longitude_value" value = "1.0" /> // 手动输入起始点经度值
<param name="origin_latitude_value" value = "1.0" /> // 手动输入起始点纬度值
<param name="origin_altitude_value" value = "1.0" /> // 手动输入起始点高度值
<param name="longitude_resolution" value = "1.09244689e-05" /> // X轴单位米对应经度变化量
<param name="latitude_resolution" value = "9.01006167e-06" /> // Y轴单位米对应纬度变化量
<param name="altitude_resolution" value = "1.0" /> // Z轴单位米对应高度变化量
<node pkg="gps_to_xyz" name="gps_to_xyz" type="gps_to_xyz" output="screen" />
<node pkg="rviz" type="rviz" name="rviz" args="-d $(find gps_to_xyz)/rviz/gps_to_xyz.rviz" />
</launch>
三、实现过程
1、首先选定一个GPS经纬高度数据点作为起始点和平面XYZ坐标系原点;
2、分别计算出X、Y、Z轴方向上单位米对应经度、纬度、高度变化量,即经纬高度分辨率,计算方式可以通过Google Earth(需要科学上网)进行测量计算;
3、修改gps_to_xyz.launch文件相应参数;
4、运行程序;
5、播放包含gps数据的rosbag包;
6、完成;
四、实现结果
GPS经纬高度值转换为平面XYZ坐标值并用RVIZ动态显示
五、总结
代码开源地址:https://gitee.com/wccworld/gps_to_xyz
创作不易,如有错误,敬请批评指正。