一文搞定GPS接收机定位

结课大作业,希望对大家有所帮助

什么是GPS接收机

GPS接收机是接收全球定位系统卫星信号并确定地面空间位置的仪器。GPS卫星发送的导航定位信号,是一种可供无数用户共享的信息资源。对于陆地、 海洋和空间的广大用户,只要拥有能够接收、跟踪、变换和测量GPS信号的接收设备, 即GPS信号接收机。

一般的GPS接收机长这样:

GPS 接收机

而我们今天要用的就比较简陋了,它长这样

使用其usb串口与电脑相连,并进行相关环境的配置,我们就可以用电脑接受其数据流了。

数据流解读

GPS接收机协议

我们利用GPS接收机接收到的数据流格式是基于NMEA-0183 协议,NMEA协议是为了在不同的GPS(全球定位系统)导航设备中建立统一的BTCM(海事无线电技术委员会)标准,由美国国家海洋电子协会(NMEA-The National Marine Electronics Associa-tion)制定的一套通讯协议。GPS接收机根据NMEA-0183协议的标准规范,将位置、速度等信息通过串口传送到PC机、MCU等设备。

NMEA-0183 协议是目前 GPS 接收机上使用最广泛的协议,大多数常见的 GPS 接收机、GPS 数据处理软件、导航软件都遵守或者至少兼容这个协议。

数据格式

1
$信息类型,x,x,x,x,x,x,x,x,x,x,x,x,x
  • $ 为起始标志;
  • , 为域分隔符;
  • * 为校验和识别符,其后两位数为校验和,代表了 $* 之间所有字符的按位异或值(不包括这两个字符);
  • \r\n 为终止符(不可见),所有的语句必须以来结束,也就是 ASCII 字符的“回车”(十六进制的 0D)和“换行”(十六进制的 0A)。

NMEA-0183 协议定义的语句非常多,但是常用的或者说兼容性最广的语句只有 GPGGA、GPGSA、GPGSV、GPRMC、GPVTG、GPGLL 等。下面给出这些常用 NMEA 0183 语句的字段定义解释:

GPRMC

Recommended Minimum Specific GPS/TRANSIT Data(RMC)推荐定位信息

1
$GPRMC,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,<10>,<11>,<12>*hh

各字段描述如下:

  • <1> UTC 时间,格式 hhmmss.ssss,代表时分秒.毫秒
  • <2> 定位状态,A=有效定位,V=无效定位
  • <3> 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • <4> 纬度半球 N(北纬)或 S(南纬)
  • <5> 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • <6> 经度半球 E(东经)或 W(西经)
  • <7> 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  • <8> 地面航向(方位角),等效于二维罗盘(000.0~359.9 度,以真北为参考基准,前面的 0 也将被传输)
  • <9> UTC 日期,DDMMYY(日月年)格式
  • <10> 磁偏角(000.0~180.0 度,前面的 0 也将被传输)
  • <11> 磁偏角方向,E(东)或 W(西)
  • <12> 模式指示(仅 NMEA0183 3.0 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)
  • 最后两个字节是校验和

注意:

  • 如果字段 4 的值等于 N,则字段 3 的值等于 ddmm.mmmmmm
  • 如果字段 4 的值等于 S,则字段 3 的值等于 -ddmm.mmmmmm
  • 如果字段 6 的值等于 E,则字段 5 的值等于 ddmm.mmmmmm
  • 如果字段 6 的值等于 W,则字段 5 的值等于 -ddmm.mmmmmm
  • 十进制北纬度数 = dd + mm.mmmmmm/60
  • 十进制南纬度数 = -(dd + mm.mmmmmm/60)
  • 十进制东经度数 = ddd + mm.mmmmmm/60
  • 十进制西经度数 = -(ddd + mm.mmmmmm/60)

GPGGA

Global Positioning System Fix Data(GGA)GPS定位信息

1
$GPGGA,<1>,<2>,<3>,<4>,<5>,<6>,<7>,<8>,<9>,M,<10>,M,<11>,<12>*hh

各字段描述如下:

  • <1> UTC 时间,hhmmss(时分秒)格式
  • <2> 纬度 ddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • <3> 纬度半球 N(北纬)或 S(南纬)
  • <4> 经度 dddmm.mmmm(度分)格式(前面的 0 也将被传输)
  • <5> 经度半球 E(东经)或 W(西经)
  • <6> GPS 状态:0=未定位,1=非差分定位,2=差分定位,6=正在估算
  • <7> 正在使用解算位置的卫星数量(00~12)(前面的 0 也将被传输)
  • <8> HDOP 水平精度因子(0.5~99.9)
  • <9> 海拔高度(-9999.9~99999.9)
  • <10> 地球椭球面相对大地水准面的高度
  • <11> 差分时间(从最近一次接收到差分信号开始的秒数,如果不是差分定位将为空)
  • <12> 差分站 ID 号 0000~1023(前面的 0 也将被传输,如果不是差分定位将为空)

GPGSA

GPS DOP and Active Satellites(GSA)当前卫星信息

1
$GPGSA,<1>,<2>,<3>,<3>,,,,,<3>,<3>,<3>,<4>,<5>,<6>,<7>

各字段描述如下:

  • <1> 模式 :M = 手动, A = 自动。
  • <2> 定位型式 1 = 未定位, 2 = 二维定位, 3 = 三维定位。
  • <3> PRN 数字:01 至 32 表天空使用中的卫星编号,最多可接收 12 颗卫星信息。
  • <4> PDOP 位置精度因子(0.5~99.9)
  • <5> HDOP 水平精度因子(0.5~99.9)
  • <6> VDOP 垂直精度因子(0.5~99.9)
  • <7> Checksum(检查位)

GPVTG

Track Made Good and Ground Speed(VTG)地面速度信息

1
$GPVTG,<1>,T,<2>,M,<3>,N,<4>,K,<5>*hh

各字段描述如下:

  • <1> 以真北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  • <2> 以磁北为参考基准的地面航向(000~359 度,前面的 0 也将被传输)
  • <3> 地面速率(000.0~999.9 节,前面的 0 也将被传输)
  • <4> 地面速率(0000.0~1851.8 公里/小时,前面的0也将被传输)
  • <5> 模式指示(仅 NMEA 0183 3.0 版本输出,A=自主定位,D=差分,E=估算,N=数据无效)

GPGSV

GPS Satellites in View(GSV)可见卫星信息

1
$GPGSV, <1>,<2>,<3>,<4>,<5>,<6>,<7>,?<4>,<5>,<6>,<7>,<8>

各字段描述如下:

  • <1> GSV语句的总数
  • <2> 本句GSV的编号
  • <3> 可见卫星的总数,00 至 12。
  • <4> 卫星编号, 01 至 32。
  • <5> 卫星仰角, 00 至 90 度。
  • <6> 卫星方位角, 000 至 359 度。实际值。
  • <7> 讯号噪声比(C/No), 00 至 99 dB;无表未接收到讯号。
  • <8> Checksum(检查位)

注意:第 <4>,<5>,<6>,<7> 项个别卫星会重复出现,每行最多有四颗卫星。其余卫星信息会于次一行出现,若未使用,这些字段会空白。

更多关于0183协议详细的介绍,可以查看《CASIC多模卫星导航接收机协议规范》

在了解了GPS接收机数据的结构后,我们就可以对我们所需要的数据进行筛选并读取了

读入定位信息(简单版)

我们先进行本地数据的读取,用GPS接收机接收到数据后,我们先将数据保存在本地,而后进行读取。

要想读取USB串口的数据,我们先要在电脑上安装相应的驱动,CH340转串口芯片支持的平台驱动齐全,支持Windows/Linux/Android/MacOS/WinCE 等各主流系统。下面就给出Windows平台下驱动官网链接和简要说明:

image-20230406102157347

下载链接

下载好后,双击EXE文件运行,弹出安装成功则说明已经完成安装。

将驱动安装完毕后,我们要想接收数据还需要一个软件,下载地址如下:

下载链接

下载好后打开就是一个这样的界面

image-20230406104926222

该软件是大虾丁丁写的一个串口调试器,在查找串口助手时都推荐使用这个,这个软件是一个免费的,一些关于该软件的问题可以查看sscom网站,将接收到的数据保存在本地后,就可以在本地读取我们需要的信息了。

读取定位信息,我们需要的定位信息是经纬度,最好加上此次定位的时间,根据之前的介绍,这些数据在GPRMC,GPGGA中都有所体现,我们直接读取即可,python代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
def read_data(file_path):  # 定义读取数据的函数
IsLineBlank = 0 # 常量,标记读入的行是否为空,为空则说明已经读取完毕,停止继续读入

Llist = [] # 存储经度
Blist = [] # 存储纬度

with open(file_path, 'r') as f:
while IsLineBlank != 1: # 读入的行不为空
line = f.readline() # 逐行读入数据
if line == '': # 判断该行是否为空,是则将IsLineBlank设置为1
IsLineBlank = 1
seprated_line = line.split(',') # 0183协议数据之间以,分隔
if seprated_line[0] == '$GPRMC' and seprated_line[2] == 'A': # 找到数据行,A表示有效定位,方可使用本次数据。
# 根据0183协议格式找到需要的数据
B = seprated_line[3] # 纬度
Btype = seprated_line[4] # 纬度的类型,N/S
L = seprated_line[5] # 经度
Ltype = seprated_line[6] # 经度的类型,W/E
UTCtime = seprated_line[1] # UTC时间
UTCdate = seprated_line[9] # UTC日期
Llist.append(int(L[:3]) + float(L[3:]) / 60) # 存储经度
Blist.append(int(B[:2]) + float(B[2:]) / 60) # 存储纬度

print(
f"20{UTCdate[4:]}.{UTCdate[2:4]}.{UTCdate[:2]} {UTCtime[:2]}:{UTCtime[2:4]}:{UTCtime[4:]}") # 格式化打印时间
print(f"B={Btype} {B[:2]}'{B[2:]},L={Ltype} {L[:3]}'{L[3:]}") # 格式化打印经纬度


if __name__ == '__main__':
read_data("SAVE2023-03-31_10-20-43.DAT")

读入定位信息(进阶版)

单单只是将数据保存在本地然后读入显然是不够的,那么有没有办法可以边连接串口接受数据边提取出我们需要的数据的,答案是肯定的,不过要用到一些python串口编程的知识。

pySerial串口编程

pySerial 是 Python 中用于操作串口的第三方模块,它支持 Windows、Linux、OSX、BSD等多个平台。如果要使用 pySerial 模块,首先必须保证 Python 版本高于 Python 2.7 或者 Python 3.4。另外,如果你是用的是 Windows 系统,那必须使用 Win7 及以上的版本。 pySerial 的安装很简单,只需要执行一条命令:

1
pip install pyserial

安装完成后,只需要在 Python 代码中使用 import serial 语句导入该模块即可。

确认端口号

1
2
3
4
5
6
7
8
9
10
11
12
13
import serial
import serial.tools.list_ports

# 获取所有串口设备实例。
# 如果没找到串口设备,则输出:“无串口设备。”
# 如果找到串口设备,则依次输出每个设备对应的串口号和描述信息。
ports_list = list(serial.tools.list_ports.comports())
if len(ports_list) <= 0:
print("无串口设备。")
else:
print("可用的串口设备如下:")
for comport in ports_list:
print(list(comport)[0], list(comport)[1])

输出结果如下:

image-20230406224100614

正是我们GPS接收机的串口

打开串口

1
2
3
4
5
6
ser = serial.Serial(port="COM5", baudrate=9600,bytesize=8,stopbits=1)  # 打开COM5,配置相关参数
if ser.isOpen(): # 判断串口是否成功打开
print("打开串口成功。")
print(ser.name) # 输出串口号
else:
print("打开串口失败。")

在使用 serial.Serial() 创建串口实例时,可以传入的参数很多,常用的参数如下(默认值用加粗标记):

  • port - 串口设备名或 None
  • baudrate - 波特率,可以是50, 75, 110, 134, 150, 200, 300, 600, 1200, 1800, 2400, 4800, 9600, 19200, 38400, 57600, 115200, 230400, 460800, 500000, 576000, 921600, 1000000, 1152000, 1500000, 2000000, 2500000, 3000000, 3500000, 4000000。
  • bytesize - 数据位,可取值为:FIVEBITS, SIXBITS, SEVENBITS, EIGHTBITS
  • parity - 校验位,可取值为:PARITY_NONE, PARITY_EVEN, PARITY_ODD, PARITY_MARK, PARITY_SPACE。
  • stopbits - 停止位,可取值为:STOPBITS_ONE, STOPBITS_ONE_POINT_FIVE, STOPBITS_TOW。
  • xonxoff - 软件流控,可取值为 True, False
  • rtscts - 硬件(RTS/CTS)流控,可取值为 True, False
  • dsr/dtr - 硬件(DSR/DTR)流控,可取值为 True, False
  • timeout - 读超时时间,可取值为 None, 0 或者其他具体数值(支持小数)。当设置为 None 时,表示阻塞式读取,一直读到期望的所有数据才返回;当设置为 0 时,表示非阻塞式读取,无论读取到多少数据都立即返回;当设置为其他数值时,表示设置具体的超时时间(以秒为单位),如果在该时间内没有读取到所有数据,则直接返回。
  • write_timeout: 写超时时间,可取值为 None, 0 或者其他具体数值(支持小数)。参数值起到的效果参考 timeout 参数。

我们根据sscom界面上的参数配置即可

image-20230406230348646

读取串口数据

1
2
3
4
5
6
while True:
com_input = ser.read(1000)
if com_input: # 如果读取结果非空,则输出
print(com_input.decode())

ser.close()

输出如下

image-20230406230941385

关于 read() 方法,需要了解如下几点: ① read() 方法默认一次读取一个字节,可以通过传入参数指定每次读取的字节数。 ② read() 方法会将读取的内容作为返回值,类型为 bytes。 ③ 在打开串口时,可以为 read() 方法配置超时时间。

以上就是python串口编程的一些基础操作,如果想要了解更多 pySerial 细节,可以参考 pySerial官方文档,结合上之前的数据读取,我们就可以实时的接收数据流并处理了

将经纬度信息在地图上显示出来(基于百度地图API)

百度地图API是为开发者免费提供的一套基于百度地图服务的应用接口,包括JavaScript API、Web服务API、Android SDK、iOS SDK、定位SDK、车联网API、LBS云等多种开发工具与服务,提供基本地图展现、搜索、定位、逆/地理编码、路线规划、LBS云存储与检索等功能,适用于PC端、移动端、服务器等多种设备,多种操作系统下的地图应用开发。

准备工作

在使用百度地图之前,我们需要拥有一个自己的百度账号,并申请注册成为百度开发者,然后我们需要创建一个浏览器端应用,就可以获取到一个唯一的服务秘钥(AK),具体操作如下:

控制台->应用管理->我的应用->创建应用

应用名自取,选择浏览器端,之后点击创建即可。

image-20230406204027293
image-20230406204102705
image-20230406204127169
image-20230406205256087

静态图

百度地图静态图API,可实现将百度地图以图片形式嵌入到您的网页中。您只需发送HTTP请求访问百度地图静态图服务,便可在网页上以图片形式显示您的地图。静态图API较之JavaScript API载入的动态网站,既能满足基本的地图信息浏览,又能加快网页访问速度。

通过给<img>标签设置src属性即可将地图图片显示在网页中。用户可以指定图片的尺寸、地图的显示范围(包含中心点和缩放级别),还可以放置一些覆盖物在地图上,以生成符合需求的地图图片。

image-20230406211205927
image-20230406211250221

让我们来详细阅读一下其服务文档

服务地址

1
https://api.map.baidu.com/staticimage/v2 //GET请求

组成说明:

服务参数列表

参数名 必选 默认值 描述
ak 用户的访问密钥。支持浏览器端和服务端ak,网页应用推荐使用 服务端ak(sn校验方式)
mcode 安全码。若为Android/IOS SDK的ak, 该参数必需。
width 400 图片宽度。取值范围:(0, 1024]。Scale=2,取值范围:(0, 512]。
height 300 图片高度。取值范围:(0, 1024]。Scale=2,取值范围:(0, 512]。
center 北京 地图中心点位置,参数可以为经纬度坐标或名称。坐标格式:lng<经度>,lat<纬度>,例如116.43213,38.76623。
zoom 11 地图级别。高清图范围[3, 18];低清图范围[3,19]
copyright pl 静态图版权样式,0表示log+文字描述样式,1表示纯文字描述样式,默认为0。
dpiType pl 手机屏幕类型。取值范围:{ph:高分屏,pl:低分屏(默认)},高分屏即调用高清地图,低分屏为普通地图。
coordtype bd09ll 静态图的坐标类型。支持wgs84ll(wgs84坐标)/gcj02ll(国测局坐标)/bd09ll(百度经纬度)/bd09mc(百度墨卡托)。默认bd09ll(百度经纬度)
scale null 返回图片大小会根据此标志调整。取值范围为1或2: 1表示返回的图片大小为size= width * height; 2表示返回图片为(width2)(height 2),且zoom加1 注:如果zoom为最大级别,则返回图片为(width2)(height2),zoom不变。
bbox null 地图视野范围。格式:minX,minY;maxX,maxY。
markers null 标注,可通过经纬度或地址/地名描述;多个标注之间用竖线分隔。
markerStyles null 与markers有对应关系。markerStyles可设置默认图标样式和自定义图标样式。其中设置默认图标样式时,可指定的属性包括size,label和color;设置自定义图标时,可指定的属性包括url,注意,设置自定义图标时需要先传-1以此区分默认图标。
labels null 标签,可通过经纬度或地址/地名描述;多个标签之间用竖线分隔。坐标格式:lng<经度>,lat<纬度>,例如116.43213,38.76623。
labelStyles null 标签样式 content, fontWeight,fontSize,fontColor,bgColor, border。与labels一一对应。
paths null 折线,可通过经纬度或地址/地名描述;多个折线用竖线"|"分隔;每条折线的点用分号";"分隔;点坐标用逗号","分隔。坐标格式:lng<经度>,lat<纬度>,例如116.43213,38.76623。
pathStyles null 折线样式 color,weight,opacity[,fillColor]。

是不是感觉很复杂,没事看个例子就行了:

1
2
https://api.map.baidu.com/staticimage/v2?ak=E4805d16520de693a3fe707cdc962045&center=116.403874,39.914889&width=400&height=300&zoom=11&markers=116.288891,40.004261|116.487812,40.017524|116.525756,39.967111|116.536105,39.872374|116.442968,39.797022|116.270494,39.851993|116.275093,39.935251|116.383177,39.923743&markerStyles=l,A|m,B|l,C|l,D|m,E|,|l,G|m,H
// 返回一张在北京地图上添加多个普通标注点的地图图片
  • AK就是你之前创建应用得到的秘钥,可以在我的应用处查看,
  • center是地图的中心点位置,经纬度之间以逗号分隔,
  • width&height是地图的宽和高,单位是像素
  • zoom是地图的级别,百度地图 API 中共有 19 个级别
在这里插入图片描述

注意:1、在项目中使用百度地图时,层级为 3 时,世界地图大小已经和容器的大小一致;2、级别 为 2 时,地图的大小是容器的一半;3、地图显示级别为 1 时,地图的长宽大约为 容器的四分之一,而且在使用时,特别不好找到地图;因此,我们在项目中使用百度地图时,一定要使用合适的地图层级。

  • markers就是你要标记的点的经纬度,多组经纬度之间以|分隔
  • markerStyles与markers有对应关系。markerStyles可设置默认图标样式和自定义图标样式。这里我们就用它给的实例中的样式就行了。

那么我们该怎么得到这个复杂的url呢?归根结底这个url就是在https://api.map.baidu.com/staticimage/v2这个服务地址的基础上添加了一些参数,有一些网络或者爬虫的知识就能很快的完成这个复杂的url的构造,不过这个url中还出现了?,&以外的符号,所以并不能直接构造,建议还是用字符串拼接而成。

1
2
3
4
5
6
7
8
9
10
11
12
ak = 你的密钥
width = 400
height = 300
zoom = 19

url = f"https://api.map.baidu.com/staticimage/v2?ak={ak}&center={Llist[0]},{Blist[0]}&width=400&height=300&zoom=19&markers="

for i in range(len(Blist)):
url = url + f"{Llist[i]},{Blist[i]}"
if i != len(Blist) - 1:
url = url + "|"
url = url + "&markerStyles=l,A|m,B|l,C|l,D|m,E|,|l,G|m,H"

输出结果如下:

image-20230407112901379

我们点击这个链接打开看一下是否能得到我们想要的地图

image-20230407112938430

发现确实是成功了。那么我们想要将图片保存在本地,只需要用一些基础的爬虫知识就可以完成了:

1
2
3
with open("map.jpg", 'wb') as f:
resp = requests.get(url)
f.write(resp.content)
image-20230407113124833

搞定!!

End~~ 撒花ฅ>ω<*ฅ花撒