前言

对GPS中的各种格式所代表的内容进行熟悉;对某一格式的GPS数据(数据文件:gps.txt)中的X,Y坐标读出,并通过界面绘出。


一、内容概述

(1)对GPS中的各种格式所代表的内容进行熟悉;
(2)对某一格式的GPS数据(数据文件:gps.txt)中的X,Y坐标读出,并通过界面绘出。代码编写可采用任何一种语言,推荐VB(实验室电脑有VB C++等)。
注意以下问题:
1)编写读取文件功能;
2)数组使用;
3)界面编写存在比例问题;
4)对于电脑屏幕的0,0坐标倒置可以忽略。

二、仪器与设备

硬件:笔记本电脑
软件:Microsoft visual studio 2019平台的OpenGl开发

三、实验原理

1.GPS模块数据格式 NMEA-0183协议

NMEA-0183协议格式,这是一个国际标准的导航数据标准协议。
GPS上电后,每隔一定的时间就会返回一定格式的数据,数据格式为:

$信息类型,x,x,x,x,x,x,x,x,x,x,x,x,x

每行开头的字符都是‘$’,接着是信息类型,后面是数据,以逗号分隔开。一行完整的数据如下:
$GPRMC,080655.00,A,4546.40891,N,12639.65641,E,1.045,328.42,170809,A*60

其中包含了卫星时间,经纬度,高程等信息,这是明码形式的数据,即对于人看起来也是有意义的数据。这些数据大多是通过串口传输接收的,通过冲串口缓冲区中读取出来,再编写相应的代码对之进行处理。
每行以字符”$”开头,以为结尾,CR—Carriage Return,LF—Line Feed,表示回车和换行。信息类型有以下几种,如表1

信息类型

描述

GPRMC

推荐定位信息

GPVTG

地面速度信息

GPGGA

全球定位信息

GPGSA

当前卫星信息

GPGSV

可见卫星信息

GPGLL

地理定位信息

在所需要分析的gps.txt中,包含了上述六种信息类型中的GPRMC、GPGGA、GPGSA、GPGSV四种,于是对这四种类型的数据的各字段内容进行分析。
其中字段0均表示帧头,在经纬度中,其数据单位格式通常为ddmm.mmmm。
对于GPRMC,字段3-6分别表示纬度、南纬/北纬、经度、东经西经。
对于GPGGA,字段2-5分别表示纬度、南纬/北纬、经度、东经西经。
对于GPGSA和GPGSV,由于表示的是卫星的状态信息,所以不对其进行处理。

2.数据处理

对于存在txt中的GPS数据,需要用到文件流进行读取。在数据读取时要对数据进行分析,将符合条件即能够表示定位信息的两种格式的数据筛选出来。得到了读取后的数据,还需要进行字符的分割,提取出有用的信息,例如经纬度这类信息。
得到经纬度后,需要将其转化为x、y坐标的形式。虽然地球是不规则的球体,但在此处,仍将其视为一个规则的球体进行计算。得知经纬度后,我们可以选一个基准点作为原点,也便于后续画点。易知一点的x、y坐标可以由该点到基准点的x、y两个方向的长度即球上的一段弧线的长度进行表示,那么x、y坐标就能很好的表示出来了(需要注意的是,本实验中各个点距离较近因此可以这样将经纬度围成的四边形近似为矩形处理,但实际经纬度的弧度并不能直接推出xy坐标)。
利用OpenGl进行绘图来表示各点的x、y坐标。由于只是画出静态的点,所以很容易实现,不在此进行赘述。

三、实验内容

1.代码如下:

gps.h
/*
 * 2022/4/14    
 * 读取gps.txt文件中的各种格式的GPS数据
 * 根据格式判断出表示经纬度的各个字段
 * 将地理位置转化为xy坐标系,使用全局投影
 * 根据输出的经纬度信息,可以将纬度31.484900、经度121.3861作为基准点
 * 将转化得到的xy坐标系的数据写入gps-xy.txt文件中
 * 利用OpenGL进行绘图表示出xy坐标
 */
#include <vector>
#include <algorithm>
#include <ostream>
#include <fstream>
#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <cmath>
#include <gl\glut.h>
using namespace std;
//数据类型转换模板函数 
template <class Type>
Type stringToNum(const string str)
{
	istringstream iss(str);
	Type num;
	iss >> num;
	return num;
}
//定义一个结构体,包含两个double变量,用于保存经纬度两个参数
//or使用class
struct Degree
{
	double latitude;//纬度
	double longitude;//经度
};
//用于读取GPRMC格式的GPS数据
Degree read_GPRMC(string line);
//用于读取GPGGA格式的GPS数据
Degree read_GPGGA(string line);
//经纬度转换为xy坐标
Degree transform(Degree degree);
//对文件输入输出流的操作,读取gps.txt,将读取结果送入gps_xy.txt,并返回存有xy坐标的vector
vector<Degree> document();
gps.cpp
#include"gps.h"

//用于读取GPRMC格式的GPS数据
Degree read_GPRMC(string line) {

	vector<string> arr;  //定义一个字符串容器
	Degree s;	//定义一个结构体

	int position = 0;
	do
	{
		string tmp_s;    position = line.find(","); //找到逗号的位置   
		tmp_s = line.substr(0, position); //截取需要的字符串    
		line.erase(0, position + 1); //将已读取的数据删去     
		arr.push_back(tmp_s);   //将字符串压入容器中  
	} while (position != -1);

	//对于GPRMC,字段3-6分别表示纬度、南纬/北纬、经度、东经西经
	if (arr[4] == "N") {
		s.latitude = stringToNum<double>(arr[3]);
	}
	else {
		s.latitude = -stringToNum<double>(arr[3]);
	}

	if (arr[6] == "E") {
		s.longitude = stringToNum<double>(arr[5]);
	}
	else {
		s.longitude = stringToNum<double>(arr[5]) - 180.000000;
	}

	return s;
}

//用于读取GPGGA格式的GPS数据
Degree read_GPGGA(string line) {

	vector<string> arr;  //定义一个字符串容器
	Degree s;	//定义一个结构体

	int position = 0;
	do
	{
		string tmp_s;    position = line.find(","); //找到逗号的位置   
		tmp_s = line.substr(0, position); //截取需要的字符串    
		line.erase(0, position + 1); //将已读取的数据删去     
		arr.push_back(tmp_s);   //将字符串压入容器中  
	} while (position != -1);

	//对于GPGGA,字段2-5分别表示纬度、南纬/北纬、经度、东经西经
	if (arr[3] == "N") {
		s.latitude = stringToNum<double>(arr[2]);
	}
	else {
		s.latitude = -stringToNum<double>(arr[2]);
	}

	if (arr[5] == "E") {
		s.longitude = stringToNum<double>(arr[4]);
	}
	else {
		s.longitude = stringToNum<double>(arr[4]) - 180.000000;
	}

	return s;
}
//经纬度转换为xy坐标
Degree transform(Degree degree) {

	double radius = 6367444.5;  //地球半径
	double PI = 3.1415926;

	Degree basepoint; //定义一个基准点经纬度的结构体
	basepoint.latitude = 31.484901;
	basepoint.longitude = 121.386169;

	Degree xy;//定义一个基准点xy坐标系的结构体

	//x坐标
	xy.latitude = (PI * radius * (degree.latitude - basepoint.latitude) / 180 - 346401700)*2;
	//y坐标
	xy.longitude = (PI * radius * (degree.longitude - basepoint.longitude) / 180 - 1335509300)*2;

	return xy;
}

//对文件输入输出流的操作,读取gps.txt,将读取结果送入gps_xy.txt,并返回存有xy坐标的vector
vector<Degree> document(){
	//定义一个数组存储xy坐标信息
	vector<Degree> point;
	//针对文件读取的流,用于从文件读取信息
	ifstream read("gps.txt");
	//针对文件写入的流,用于从文件写入信息
	ofstream write("gps-xy.txt");

	//使用<<读取数据,实现逐行读取,成功解决之前仅仅读取第一行的情况
	if (!read.fail() && !write.fail())
	{
		//判断文件是否为空,抑或是判断其是否读到文件结尾
		while (!read.eof()) {
			string line;
			read >> line;
			//定义一个结构体
			Degree degree, xy;

			//在一个字符串中查找一个指定的单个字符或字符数组。如果找到,就返回首次匹配的开始位置即0,否则返回为空
			if (line.find("$GPRMC") == 0)
			{
				degree = read_GPRMC(line);
				xy = transform(degree);
				write << "$GPRMC:纬度:" << setprecision(6) << fixed << degree.latitude / 100
					<< "  经度:" << setprecision(6) << fixed << degree.longitude / 100
					<< "  x坐标:  " << setprecision(6) << fixed << xy.latitude
					<< "  y坐标:  " << setprecision(6) << fixed << xy.longitude << endl;
				point.push_back(xy);   //将xy压入容器中

			}
			else if (line.find("$GPGGA") == 0)
			{
				degree = read_GPGGA(line);
				xy = transform(degree);
				write << "$GPGGA:纬度:" << setprecision(6) << fixed << degree.latitude / 100
					<< "  经度:" << setprecision(6) << fixed << degree.longitude / 100
					<< "  x坐标:  " << setprecision(6) << fixed << xy.latitude
					<< "  y坐标:  " << setprecision(6) << fixed << xy.longitude << endl;
				point.push_back(xy);   //将xy压入容器中
			}

		}
	}
	read.close();
	write.close();

	return point;
}
main.cpp
#include<iostream>
#include<gl/glut.h>
#include"gps.h"
using namespace std;

//画点
void drawPoint(Degree point) {
	glVertex2d(point.latitude, point.longitude);
	//cout << point.latitude << " xy "  << point.longitude << endl;
}
//对vector中存有的xy坐标进行遍历,为画点做准备
void display() {
	vector<Degree> point = document();

	glClear(GL_COLOR_BUFFER_BIT); //清除颜色缓冲以及深度缓冲,把整个窗口清除为黑色的任务
	glClearColor(1.0, 1.0, 1.0, 0);  //设置背景颜色为白色 
	glColor3f(1.0, 0.0, 0.0);		//设置为红色	
	glPointSize(6);//glPointSize()不能放在glBegin()和glEnd()之间,要放在glBegin()之前
	glBegin(GL_POINTS);
	for (int i = 0; i < point.size(); ++i) {
		drawPoint(point[i]);
	}
	glEnd();
	glFlush();
	glutSwapBuffers();	// 交换缓冲区
}

int main(int argc, char* argv[]) {

	glutInit(&argc, argv);              //初始化
	glutInitDisplayMode(GLUT_RGB | GLUT_SINGLE);  //设置初始显示模式
	gluOrtho2D(0.0, 500, 0.0, 500);//窗口坐标左下角(0,0), 右上角(500,500) 用来指定屏幕区域对应的模型坐标范围
	glutInitWindowSize(500, 500);      //设置窗口大小
	glutCreateWindow("画点");     //设置窗口名称
	glutDisplayFunc(&display);   //函数负责渲染,调用该函数
	glMatrixMode(GL_PROJECTION);        //指定设定投影参数
	gluOrtho2D(0.0, 500, 0.0, 500);            //设置投影参数
	glutMainLoop();

	return 0;
}

2.运行结果

每次运行后,都会对gps-xy.txt中的内容进行覆盖。下图展示了利用OpenGL画出的各个点之间的位置关系:

gps字段python解析 python读取gps_#include

四、实验感想

在实验中,我遇到过很多问题,如:在读取gps.txt时,如何设置while的条件以实现对文件的逐行读取而非仅仅读取第一行,如何存储和返回经纬度以及x、y坐标两个参数,如何实现将经纬度转化为x、y坐标,绘图时也遇到了很多麻烦,但是幸好有上学期所学的计算机图形学的基础,所以使用OpenGL也能够很容易的实现。我查阅了很多资料,才最终解决了各种问题,并成功完成了此次实验。
在解决问题的过程中,我对于以前所习得的知识进行了巩固,如对vector、OpenGL的温习,并用自己的方式来思考解决问题的方式,对于自己的思考能力以及编程能力都有了一定程度的锻炼。