在嵌入式平台上將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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM