还记得这段动画么,从飞控的 microSD 中直接点击一个路径文件,即可打开谷歌地图并显示出路径。并且这个文件是由飞控在飞行过程中实时生成的,不需要额外的编辑,使用起来非常方便。

因为 typecho 的原因,没办法上传大于 1M 的 GIF 动画。效果请看视频:youtube youku 1:35 处开始


想要在飞控中记录这个谷歌地球的描述文件,是非常简单的。

1. 背景知识

谷歌地球/地图 (google earth / google map) 使用的描述文件为 .kml 文件。本质上为 xml 文件。XML 大家应该熟悉了

可扩展标记语言(英语:eXtensible Markup Language,简称: XML),是一种标记语言。标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种信息的文章等。如何定义这些标记,既可以选择国际通用的标记语言,比如HTML,也可以使用像XML这样由相关人士自由决定的标记语言,这就是语言的可扩展性。XML是从标准通用标记语言(SGML)中简化修改出来的。它主要用到的有可扩展标记语言、可扩展样式语言(XSL)、XBRL和XPath等。[1]

上面说了一堆,意思就是说,.kml 呢,实际上就是一个文本文件,里面用文本记录各种描述,比如线,颜色图形,位置等等。

Google earth/map 开发手册里面有详细的 kml 文件的开法指南。
英文在这中文在这

其实,要做这个路径记录文件,并不需要去翻手册。

2. 这样是不是很简单

窍门就在这: 在描述路径的时候,.kml 文件的头和尾都可以是固定不变的,唯一变化的是,路径中各个点的经纬度值。

这么说就简单了吧?我们的飞控只需要把固定的文件头写进一个 .kml 的文本文件,然后根据 GPS 实时返回的经纬度和高度信息,不断地在文件后追加当前的位置信息,一直写入直到你不想记录的时候,再把固定的文件尾追加到文件中。保存,搞定!

不说别的,先看一个典型的(花俏的) kml 文件。大家可以将以下保存到一个 .kml 文件里,拖到 google earth 里面看看效果。

<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
    <Document>
<name>Paths</name>
<description> DBP V0.4 KML LOG</description>
<Style id="yellowLineGreenPoly">
    <LineStyle>
        <color>7f00ffff</color>
        <width>4</width>
    </LineStyle>
    <PolyStyle>
        <color>5f00ff00</color>
    </PolyStyle>
</Style>
<Placemark>
    <name>Absolute Extruded</name>
    <description>Deep Blue Plane V0.4 Path</description>
    <styleUrl>#yellowLineGreenPoly</styleUrl> 
    <LineString>
        <extrude>1</extrude>
        <tessellate>1</tessellate>
        <altitudeMode>absolute</altitudeMode>
            <coordinates>
                110.411100,25.319343,194.000000
                110.411200,25.319243,194.000000
                110.411092,25.319144,194.000000
            </coordinates>
     </LineString>
    </Placemark>
</Document>
</kml>

按到关键没? “110.411092,25.319343,174.000000”
具体是哪,自己有兴趣可以去看看。

至于格式问题,经度范围从 -180 到 +180? 负数代表西经,正数代表东经。维度范围从 -90 到 +90, 负数代表南纬,正数代表北纬。单位全部是度,所以不会有几分几秒的情况,一个实数就代表了一个点。高度为海拔高度,单位是米。

看到文件的中间部分,由 <coordinates> </coordinates> 包起来的部分,每一行表示一个点,每一个点坐标,一次由经度,纬度和海拔高度组成,这三个信息都可以方便地从任何“正常的” GPS 输出中找到。

这里例子的文件,由三个位置坐标点组成,实际飞控记录的时候,这一部分将不断增加,将是整个文件中占用行数最多的部分。

在看到文件前部分,

<Style id="yellowLineGreenPoly">
    <LineStyle>
        <color>7f00ffff</color>
        <width>4</width>
    </LineStyle>
    <PolyStyle>
        <color>5f00ff00</color>
    </PolyStyle>
</Style>

这部分描述了你的路径画出来的风格,具体可以去看手册里面,或者直接修改一些配色,反正我这是已经是最丑的风格了。。

于是上面的代码风格就是如此逗逼

三个顶点,即为坐标里面描述的三个点,高度为海拔 194米。

只要不断记录这些点,就能实现动画里面的飞行路径了。

那么,飞控里面咋生成 .kml 文件呢?

2. 那我们来看看代码

与 .kml 文件生成相关的代码在 飞控主板源码.rar 的以下目录
program v1.0\RT-Thread_1.2.0\bsp\stm32f40x\user\thread_log.c

什么你没有代码? 代码在这里下载: 四轴毕业设计初说 - DBP飞控的开源与简介

这个文件,包含了一些独立的储存飞控运行信息,传感器信息,GPS 记录等等的线程。其中一个即为生成 .kml 文件的线程 void entry_thread_kml_log(void* parameter)


//记录kml文件
void entry_thread_kml_log(void* parameter)
{    
    int fd = -1;
    int    size;
    int gps_count_bef = 1;
    char buf[512];
    char dir[64];
    unsigned int buf_offset = 0;        //指示上一次写到哪里
    
    unsigned long long file_byte_seek = 0;            //指示文件长度
    unsigned int head_len = 0;                        //文件头尾的长度
    unsigned int tail_len = 0;
    const char file_name[] = "path.kml";
    const char head[] = 
    "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
    "<kml xmlns=\"http://www.opengis.net/kml/2.2\">\n"
    "\t<Document>\n"
    "<name>Paths</name>\n"
    "<description> DBP V0.4 KML LOG</description>\n"
    "<Style id=\"yellowLineGreenPoly\">\n"     
    "    <LineStyle>\n"       
    "        <color>7f00ffff</color>\n"      
    "        <width>4</width>\n"    
    "    </LineStyle>\n"    
    "    <PolyStyle>\n"       
    "        <color>5f00ff00</color>\n"    
    "    </PolyStyle>\n"   
    "</Style>\n"
    "<Placemark>\n"
    "    <name>Absolute Extruded</name>\n"     
    "    <description>Deep Blue Plane V0.4 Path</description>\n"      
    "    <styleUrl>#yellowLineGreenPoly</styleUrl> \n"    
    "    <LineString>\n"      
    "        <extrude>1</extrude>\n"       
    "        <tessellate>1</tessellate>\n"       //这一句是有没有竖线
    "        <altitudeMode>absolute</altitudeMode>\n"       
    "            <coordinates>\n";
    
    const char tail[] =    
    "            </coordinates>\n "    
    "    </LineString>\n"
    "    </Placemark>\n"
    "</Document>\n"
    "</kml>\n";
    
    //预先记录文件头尾长度
    head_len = strlen(head);
    tail_len = strlen(tail);
    
    //复制工作区 目录
    strcpy(dir, log_dir);
    //打开记录文件
    strcat(dir, file_name);
    
    //try to open the working file
    while(fd < 0)
    {
        int count = 0;        
        fd = open(dir, O_WRONLY | O_CREAT, 0);
        rt_thread_delay(RT_TICK_PER_SECOND);
        
        if ((count ++ )> 60) //60 seconds
        {
            rt_kprintf("kml: open file for write failed\n");
            return;
        }
    }

    rt_kprintf("google earth file'path.kml' has been created.\n");
    //写入文件头
    write(fd, head, head_len);
    file_byte_seek += head_len;
    
    //主循环
    while(1)
    {
        while(gps_count_bef >= gps.count)
        {
            rt_thread_delay(5);
        }
        gps_count_bef = gps.count;       //mark for 1 second a cycle
        
        //等待直到数据有效
        if(gps.flag == 0)
            continue;
                
        sprintf(buf,"\t\t\t\t%f,%f,%f\n",
                                        gps.lon, 
                                        gps.lat,
                                        gps.altitude);    
        
        //将缓存区写入文件
        size = write(fd, buf, strlen(buf));
        file_byte_seek += size;                //记录写入长度    
        
        if(size>0)
        {
        //rt_kprintf(buf);
        }
        else
        {
            rt_kprintf("kml log: Write to file wrong!\n");
            while(1)
                rt_thread_delay(1000);  //相当于停止这个线程
        }
        
        
        //写入文件尾
        write(fd, tail, tail_len);
        close(fd);
        fd = open(dir, O_WRONLY | O_CREAT | O_APPEND, 0);//末尾重新打开        

        //覆盖 从文件头开始计算写入了多少有效数据,覆盖文件尾
        lseek(fd, file_byte_seek, DFS_SEEK_SET);
    }
}

注释很清楚啦,我就不多说啦。
RT-Thread 支持 POSIX 标准文件系统接口,所以我的飞控可以直接使用 open() write() close() 之类的POSIX 文件接口操作文件。 如果你跑在自己的飞控,自己的 MCU,或者直接裸奔,那文件接口需要改成你的文件系统提供的接口。

3. 经纬度,高度数据怎么取?

这里就要说到如何 GPS 数据啦,这部分本不是这篇的内容,这里大概提一下。

一般 GPS 输出,至少都有标准的 NMEA-0183协议输出,可以参考这个文件 NMEA-0183 定义。 官方文档似乎是需要购买的。

GPS 会通过串口输出一大堆包含导航信息的语句,一般在10Hz(每秒 10 条)以下。大部分 GPS 模块也可以选择性输出部分语句,屏蔽部分语句。(因为很多语句信息是重复的,比如 GGA 语句和 RMC 语句都包同样的含经纬度信息)[2]

如果不想看文档,那就直接看下面的例子
这是最常用的 GPS 返回的 NMEA-0183 语句之一 GGA 语句:
$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47

     GGA          Global Positioning System Fix Data
     123519       Fix taken at 12:35:19 UTC
     4807.038,N   Latitude 48 deg 07.038' N
     01131.000,E  Longitude 11 deg 31.000' E
     1            Fix quality: 0 = invalid
                               1 = GPS fix (SPS)
                               2 = DGPS fix
                               3 = PPS fix
                               4 = Real Time Kinematic
                               5 = Float RTK
                               6 = estimated (dead reckoning) (2.3 feature)
                               7 = Manual input mode
                               8 = Simulation mode
     08           Number of satellites being tracked
     0.9          Horizontal dilution of position
     545.4,M      Altitude, Meters, above mean sea level
     46.9,M       Height of geoid (mean sea level) above WGS84
                      ellipsoid
     (empty field) time in seconds since last DGPS update
     (empty field) DGPS station ID number
     *47          the checksum data, always begins with *

以上说明应该比较清晰啦,其他语句也是一样的,请参考这里
这里面,有我们需要的经纬度信息和海拔高度,4807.038,N 01131.000,E 545.4,M 只是,这表示方法是不是跟我们上面不同? 小伙们,自己想办法转换吧~ 如果需要查看C语言版本的,在program v1.0\RT-Thread_1.2.0\bsp\stm32f40x\user\thread_gps.c 里面找到 GPS 其中几个常用语句的解码函数。

4. 其他说

其实这种方式并不好,原因是我的飞控没有掉电保存能力,经常记录到一半,没写文件尾就掉电了,导致得自己手动编辑文件,加入文件尾。

另一种方法,是在飞控中直接记录所有 GPS 输出的 NMEA-0183 语句,等回到电脑上,再用脚本语言工具,例如 python 来转换成 .kml 文件。 只是,占用空间会比 .kml 本身大很多,并且增加了一个转换的步骤,略麻烦。

再另一种,我记得 google earth 有直接导入 NMEA-0183 的能力,具体如何实现,各位自行脑补。

.kml 文件是很神奇的,除了记录路径,还能做很多很多的事情。具体请参考文章刚开始提供的 .kml 开发指南。英文在这中文在这

参考文章:
四轴毕业设计初说 - DBP飞控的开源与简介

喜欢的话给个赞吧~
100个赞之后放出 python 版本的 .kml 生成器。

References:

[1]‘XML’, 维基百科,自由的百科全书. 06-Jan-2016.

[2]‘NMEA data’. [Online]. Available: http://www.gpsinformation.org/dale/nmea.htm. [Accessed: 29-May-2016].