ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

在嵌入式平台上将GPS/北斗模块获取的经纬度转换为百度地图经纬度

2021-08-21 16:03:35  阅读:232  来源: 互联网

标签:tmp return 经纬度 stream ret 嵌入式 buf 百度 GPS


一、前言

  最近需要做一个嵌入式系统显示地图的项目,百度地图给我们留出了API接口可以调用。百度地图API网址为:https://lbsyun.baidu.com

  之前已经有同事做好了地图获取的程序,但是显示的位置和实际位置大概有1km的偏差,上网查阅各种资料,试过各种经纬度转换的函数,最后得到的结果都很差。最后在百度地图常见问题的一栏中见到了以下内容:

  “5、百度坐标为何有偏移?

  国际经纬度坐标标准为WGS-84,国内必须至少使用国测局制定的GCJ-02,对地理位置进行首次加密。百度坐标在此基础上,进行了BD-09二次加密措施,更加保护了个人隐私。百度对外接口的坐标系并不是GPS采集的真实经纬度,需要通过坐标转换接口进行转换。

  根据以上信息可以知道,既然是百度地图官方加密,那么想要得到正确的百度地图坐标,就只能通过百度地图给出的坐标转换接口进行转换。其他任何民间的转换方法都不可靠,因为就算有人找到了转换方法,百度地图官方也可以改。

二、启动百度地图服务,获取转换过后的经纬度

  点击菜单栏的“开发文档”->“服务接口”->“Web服务API”,

  

 在左侧服务选择栏中点击“坐标转换”,如下图:

  

   这里就是坐标转换的服务介绍。

  在使用服务之前,要先完成“登录百度账号”->“申请成为百度开发者”->“获取服务秘钥(ak)”,在官方网页上有指引教程。

  或者登录百度账号后,在“控制台”->“应用管理”->“我的应用”中点击“创建应用”,如下图:

  

 

   在弹出的界面中应用名称名字随便取,应用类型选浏览器端,启用服务里面的”静态图”和“坐标转换”都要勾选(本次只使用坐标转换服务,以后会用到静态图),Referer白名单里写一个“*”即可,然后点击提交。

  

   这时在应用列表中就能看到刚才创建的应用了, 并且有一个AK,如下图:

  

  将这个AK复制下来,在网页中打开“坐标转换”的“服务文档”,这里有一个网址,如下图:

  

    里面的参数都有介绍,但一般不用改,只要将ak填入自己的ak,并且经纬度填入自己用GPS/北斗模块获取到的经纬度即可,然后将这一串网址复制到浏览器中打开即可。浏览器会直接显示返回结果,如下:

  {"status":0,"result":[{"x":108.99576850316559,"y":34.38357890574941}]}

  这是一个json的数据格式,status结果为0表示转换成功,result中的x表示的就是转换后的经度,y表示的就是转换后的纬度。

  有些人可能会问,为什么没有指定是东半球还是西半球,没有指定是南半球还是北半球,我只能说没必要。如果想获取境外地图,百度地图有境外地图的服务。

三、通过wireshark抓取HTTP数据包

  上节只是通过浏览器得到了转换后的坐标,如果要在linux下编程实现,需要将自己“冒充”为浏览器。

  首先,我们需要知道,浏览器到底给谁发送了什么东西,这个时候就要用到一个非常强大的网络抓包工具:wireshark。另外,在浏览器中输入的网址是以“https”开头的,这是加密过的,就算把包抓出来也看不出里面的内容。直接删掉“https”中的“s”,将删掉“s”的网址放到浏览器中照样能够得到转换后的经纬度,但是这样就是明文传输了,我们就可以看出里面的数据了。

  打开wireshark,双击正在上网的网卡(我的是WLAN 5),如下图:

  

  这时wireshark抓出来很多包,我们需要设置一下过滤规则,在过滤器中输入“http”后按回车,如下图:

  

   一下子过滤掉很多数据,这时在浏览器中输入坐标转换的网址,注意要把前面的https改为http,当浏览器得到转换结果之后停止wireshark的抓包,这时wireshark抓到很多包,如下图:

  

  从抓到的数据中很容易分辨出哪些是和百度地图相关的数据包,这样我们就知道了目标IP地址为220.181.43.101,为了再次过滤掉不必要的干扰,在过滤规则里加上ip.addr==220.181.43.101,这样就只剩和百度地图相关的数据了,如下图:

  

  任选一条,右键->追踪流->HTTP流,如下图:

   

    在弹出的界面中能够看到,红色区域是源(自己)向目标(百度地图)发出的数据,蓝色区域是目标(百度地图)向源(自己)返回的数据,其中就有我们想要的转换过后的数据,如下图:

  

   按照上图中红色部分发送的请求包编写程序(因为第1段红色部分发出去之后,百度地图就返回了转换过后的经纬度,因此不需要再发送第2段红色部分的数据了,然鹅我也不知道第二段红色部分数据发出去是为了什么)。特别注意,数据中换行的地方是真的换行符,windows中的换行符为“\r\n”,并且最后一行是个空行,空行也必须发出去。发送的内容是HTTP请求包,想知道包里各部分代表什么意义可以去搜索“HTTP请求报文格式”。

  编写的程序是将接收到的报文打印出来,具体代码先不放出来, 最后会放出完整版代码。程序运行结果如下:

  

  可以看到,接收到的数据前面部分和抓包抓出来的数据一样,最后那部分,也就是包含了我们要的经纬度数据的那部分打印出来却是乱码。这是为什么呢,首先,在wireshark中能看到原始数据,如下图:

  

   在“Server: apache\r\n”后面还有一个空行(这可以作为报文内容的起始标志),后面的数据确确实实不是在wireshark中看到的json数据。但是在前面打印出来的数据中能看到,“Content-Encoding: gzip”这一行,说明内容是用gzip编码过的。想要得到最终的数据还得用gzip解码。

四、将经纬度数据解码出来

  4.1)用Ubuntu自带的gzip命令解码

  通过编程,可以将数据内容保存到一个文件中,该文件是用gzip编码过的二进制文件,文件后缀必须为.gz,例如contet.txt.gz,之后输入命令,gzip -d contet.txt.gz,那么contet.txt.gz就会变成一个名为contet.txt的新文件,这个文件就是解码过后的文件。打开该文件就可以看到里面的json数据。接下来编写程序将文件中的内容读取出来再解析即可,如果有cJSON库可以用cJSON库解析,这里数据比较简单,用字符串解析也很容易。

  编写程序时,调用函数system("gzip -d contet.txt.gz");就相当于在命令行输入了gzip -d contet.txt.gz。

  4.2)用zlib库解码

  4.1中的方法需要在嵌入式linux平台中支持gzip命令,显然一般的平台是不支持这条命令的。原本以为自己要手撕代码,但幸运的是在网上找到一篇博客,上面的代码可以直接使用,博客链接如下:https://blog.csdn.net/weixin_28607671/article/details/116988589

  将这份代码移植过来之后可以直接使用,我在上面的基础上稍微进行了一些改动,原代码解码之后会少两个字节,我改动后不会少字节了。

 五、完整代码

  1 /**
  2  * filename: bdmap_coord.c
  3  * author: Suzkfly
  4  * date: 2021-08-21
  5  * platform: linux
  6  * 将GPS/北斗模块的经纬度转换为百度地图经纬度。编译时要加-lz参数,第26行的AK要改为自己的AK
  7  */
  8 #include <sys/types.h> 
  9 #include <sys/socket.h>
 10 #include <stdio.h>
 11 #include <string.h>
 12 #include <netinet/ip.h>
 13 #include <netinet/in.h>
 14 #include <arpa/inet.h>
 15 #include <stdlib.h>
 16 #include <sys/stat.h>
 17 #include <fcntl.h>
 18 #include <unistd.h>
 19 #include <zlib.h>
 20 
 21 #define SER_PORT    80                  /* HTTP请求端口固定为80 */
 22 #define SER_ADDR    "220.181.43.101"    /* 百度地图服务IP地址 */
 23 
 24 #define ORIGINAL_LON    "108.9844475"   /* 原始经度 */
 25 #define ORIGINAL_LAT    "34.37899"      /* 原始纬度 */
 26 #define AK              "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"  /* AK,填入自己的AK */
 27 //https://lbsyun.baidu.com/
 28 
 29 /**
 30  * \brief gzip解压
 31  *
 32  * \param[in] pSrc:需要解压的数据首地址
 33  * \param[in] srcSize:需要解压的数据长度
 34  * \param[out]:pOutDest:存放解压后的数据的二级指针
 35  * \param[out]:pOutBufSize:解压后的数据长度
 36  *
 37  * \retval 成功返回0,失败返回-1
 38  *
 39  * \note gzip和zip解压大致相同,但是他们的头数据大小不一样,这个得注意,用inflateInit2(&d_stream,47)
 40  * \note pOutBufSize可以传入NULL
 41  */
 42 int vidpeek_uncompressGzip (unsigned char* pSrc, unsigned int srcSize, char**pOutDest, unsigned int* pOutBufSize)
 43 {
 44     char* pBuf = pSrc + (srcSize - 1);
 45     unsigned int len = *pBuf;
 46     int uncompressResult;
 47     z_stream d_stream;  
 48     int i = 0;
 49     
 50     if ((pSrc == NULL ) || (pOutDest == NULL) || (*pOutDest == NULL)) {
 51         return -1;
 52     }
 53  
 54     //printf("#############pSrc 0x%x 0x%x 0x%x 0x%x", pSrc[0], pSrc[1], pSrc[2], pSrc[3]);
 55     //check gz file,rfc1952 P6
 56     if((*pSrc !=0x1f)||(*(pSrc+1) != 0x8b)) {
 57         printf("\nuncompressGzip non Gzip\n");
 58         return -1;
 59     }
 60     for (i = 0; i < 3; i++) {
 61         pBuf--;
 62         len <<= 8;
 63         len += *pBuf;
 64     }
 65  
 66     //fortest
 67     if((len == 0) || (len > 1000000)) {
 68         printf("\nuncompressGzip,-1or gzip!\n");
 69         return -1;
 70     }
 71  
 72     //gzipdecompression start!!!
 73     d_stream.zalloc =Z_NULL;
 74     d_stream.zfree =Z_NULL;
 75     d_stream.opaque = Z_NULL;
 76     d_stream.next_in =Z_NULL;
 77     d_stream.avail_in= 0;
 78     uncompressResult =inflateInit2(&d_stream,47);
 79     if(uncompressResult!=Z_OK) {
 80         printf("\ninflateInit2 -1or:%d\n",uncompressResult);
 81         return uncompressResult;
 82     }
 83  
 84     d_stream.next_in = pSrc;
 85     d_stream.avail_in = srcSize;
 86     d_stream.next_out = (char *)*pOutDest;
 87     d_stream.avail_out = len + 2;   /* Modify by Suzkfly,原本这里是不+2的,但是解析出来会少2个字符 */
 88     uncompressResult =inflate(&d_stream, Z_NO_FLUSH);
 89  
 90     switch(uncompressResult) {
 91         case Z_NEED_DICT:
 92             uncompressResult = Z_DATA_ERROR;
 93         case Z_DATA_ERROR:
 94         case Z_MEM_ERROR:
 95             (void)inflateEnd(&d_stream);
 96             return uncompressResult;
 97     }
 98  
 99    //printf("outlen= %d, total_in= %d, total_out= %d, avail_out= %d@@@@@@@@@@@\n",len, d_stream.total_in, d_stream.total_out, d_stream.avail_out);           
100  
101     inflateEnd(&d_stream);
102     if (pOutBufSize != NULL) {
103         *pOutBufSize = len;
104     }
105     
106     return 0;
107 }
108 
109 /**
110  * \brief 将GPS/北斗模块的经纬度转换为百度地图经纬度
111  *
112  * \param[in]: p_lon_gps:GPS/北斗模块得到的经度
113  * \param[in]: p_lat_gps:GPS/北斗模块得到的纬度
114  * \param[out]:p_lon_bdmap:百度地图经度
115  * \param[out]:p_lat_bdmap:百度地图纬度
116  *
117  * \retval 成功返回0,内部错误返回-1,转换失败返回-2,参数错误返回-3
118  */
119 int gps_to_bdmap (const char *p_lon_gps, const char *p_lat_gps, char *p_lon_bdmap, char *p_lat_bdmap)
120 {
121     int ret, i;
122     int sockfd;
123     struct sockaddr_in seraddr;
124     char buf[4096] = { 0 };
125     int count = 0;                  /* 接收到的字节个数 */
126     char *p_tmp = NULL;             /* 定义2个临时指针 */
127     char *p_tmp2 = NULL;
128     struct timeval tv_out;          /* 设定超时时间 */
129     unsigned int contet_size = 0;   /* 包含了经纬度的数据长度 */
130     char len_a[8] = { 0 };
131     int fd = 0;
132     int len = 0;
133 
134     
135     /* 检查参数 */
136     if ((p_lon_gps == NULL) || (p_lat_gps == NULL) || 
137         (p_lon_bdmap == NULL) || (p_lat_bdmap == NULL)) {
138         return -3;
139     }
140     
141     /* 创建socket套接字 */
142     seraddr.sin_family = AF_INET;
143     seraddr.sin_port = htons(SER_PORT);
144     seraddr.sin_addr.s_addr = inet_addr(SER_ADDR);
145     sockfd = socket(AF_INET, SOCK_STREAM, 0);
146     if (-1 == sockfd) {
147         perror("fail to socket\n");
148         return -1;
149     }
150 
151     /* 建立连接 */
152     ret = connect(sockfd, (struct sockaddr *)&seraddr, sizeof(seraddr));
153     if (-1 == ret) {
154         perror("fail to connect\n");
155         return -1;
156     }
157     
158     /* 发送HTTP请求报文 */
159     sprintf(buf, "GET /geoconv/v1/?coords=%s,%s&from=1&to=5&ak=%s HTTP/1.1\r\n", p_lon_gps, p_lat_gps, AK);
160     strcat(buf, "Host: api.map.baidu.com\r\n");
161     strcat(buf, "Connection: keep-alive\r\n");
162     strcat(buf, "Cache-Control: max-age=0\r\n");
163     strcat(buf, "Upgrade-Insecure-Requests: 1\r\n");
164     strcat(buf, "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.159 Safari/537.36\r\n");
165     strcat(buf, "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n");
166     strcat(buf, "Accept-Encoding: gzip, deflate\r\n");
167     strcat(buf, "Accept-Language: zh-CN,zh;q=0.9\r\n");
168     strcat(buf, "Cookie: BAIDUID=1179C0DFEA5AE34FB5EAB79461EB442B:FG=1\r\n\r\n");
169     ret = send(sockfd, buf, strlen(buf), 0);
170     if (-1 == ret) {
171         perror("fail to send\n");
172         return -1;
173     }
174     
175     /* 设定接收数据超时时间为1S。这里要根据设备网络情况而定 */
176     tv_out.tv_sec = 1;
177     tv_out.tv_usec = 0;
178     setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv_out, sizeof(tv_out));
179     
180     /* 接收HTTP响应报文 */
181     p_tmp = buf;
182     count = 0;
183     memset(p_tmp, 0, strlen(p_tmp));
184     while ((ret = recv(sockfd, p_tmp, sizeof(buf) - count, 0)) > 0) {
185         count += ret;
186         p_tmp += ret;
187     }
188     close(sockfd);  /* 关闭套接字 */
189     
190     /* 打印接收到的报文。这里不按字符串打印是因为接收到的数据中可能会包含'\0' */
191     //printf("count = %d\n", count);
192     //for (i = 0; i < count; i++) {
193     //    printf("%c", buf[i]);
194     //}
195     //fflush(stdout);     /* 刷新输出缓冲区 */
196     
197     /* 解析出被编码的数据长度 */
198     p_tmp = strstr(buf, "Content-Length: ");
199     if (p_tmp == NULL) {
200         perror("fail to strstr\n");
201     }
202     p_tmp += strlen("Content-Length: ");
203     p_tmp2 = strstr(p_tmp, "\r\n");
204     strncpy(len_a, p_tmp, p_tmp2 - p_tmp);
205     contet_size = atoi(len_a);
206     //printf("len = %d\n", contet_size);
207     
208     /* 找到数据内容起始地址 */
209     p_tmp = strstr(buf, "\r\n\r\n");
210     if (p_tmp == NULL) {
211         perror("fail to strstr\n");
212     }
213     p_tmp += strlen("\r\n\r\n");
214 
215 #if 0   /* 使用系统自带的gzip命令解码 */
216     /* 将数据内容保存到文件中 */
217     fd = open("contet.txt.gz", O_RDWR | O_CREAT | O_TRUNC, 0666);
218     if (fd < 0) {
219         perror("fail to open\n");
220         return -1;
221     }
222     ret = write(fd, p_tmp, contet_size);
223     if (ret != contet_size) {
224         perror("fail to write\n");
225         return -1;
226     }
227     close(fd);
228     
229     /* 用gzip解压,解压后的文件名为contet.txt */
230     system("gzip -d contet.txt.gz");
231     
232     /* 打开解压后的文件,得到json数据 */
233     fd = open("contet.txt", O_RDONLY);
234     if (fd < 0) {
235         perror("fail to open\n");
236         return -1;
237     }
238     memset(buf, 0, sizeof(buf));
239     read(fd, buf, sizeof(buf));
240     //printf("buf = %s\n", buf);
241     close(fd);
242     
243     system("rm contet.txt");    /* 删除中间文件 */
244 #else   /* 使用zlib库进行解码 */
245     p_tmp2 = buf;
246     ret = vidpeek_uncompressGzip(p_tmp, contet_size, &p_tmp2, &len);
247     if (ret != 0) {
248         perror("fail to decode\n");
249         return -1;
250     }
251     memset(&buf[len], 0, sizeof(buf) - len);
252 #endif
253 
254     printf("buf = %s\n", buf);  /* 可以将解析后的结果打印出来 */
255     printf("len = %d\n", len);
256     /* 解析json,得到转换过后的经纬度。由于数据简单,这里就自己解析了,若需要全面解析可以用cJSON库 */
257     p_tmp = strstr(buf, "status");
258 
259     /* 判断转换状态是否成功 */
260     p_tmp = p_tmp + strlen("status") + 2;
261     if (*p_tmp != '0') {
262         printf("parse failed\n");
263         return -2;
264     }
265     
266     /* 得到经度 */
267     p_tmp = strstr(buf, "\"x\"");
268     p_tmp += 4;
269     p_tmp2 = p_tmp;
270     while (*p_tmp2 != ',') {
271         p_tmp2++;
272     }
273     strncpy(p_lon_bdmap, p_tmp, p_tmp2 - p_tmp);
274     
275     /* 得到纬度 */
276     p_tmp = strstr(p_tmp2, "\"y\"");
277     p_tmp += 4;
278     p_tmp2 = p_tmp;
279     while (*p_tmp2 != '}') {
280         p_tmp2++;
281     }
282     strncpy(p_lat_bdmap, p_tmp, p_tmp2 - p_tmp);
283         
284     return 0;
285 }
286 
287 /**
288  * \brief example
289  */
290 int main(int argc, const char *argv[])
291 {
292     int ret = 0;
293     char lon[32] = { 0 };
294     char lat[32] = { 0 };
295 
296     ret = gps_to_bdmap(ORIGINAL_LON, ORIGINAL_LAT, lon, lat);
297     if (ret < 0) {
298         printf("ret = %d\n", ret);
299         return -1;
300     }
301     
302     printf("lon = %s\n", lon);
303     printf("lat = %s\n", lat);
304     
305     return 0;
306 }

 

标签:tmp,return,经纬度,stream,ret,嵌入式,buf,百度,GPS
来源: https://www.cnblogs.com/Suzkfly/p/15166696.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有