学习到了如何通过经纬度和海拔设置大疆无人机航迹飞行,记录如下:
1. 使用的软件是:DJI Pilot
2.通过航迹点(经纬度以及海拔)csv文件转为kml文件。(KML文件主要用于描述地球上的地理特征,如点、线、多边形、图层、图标等,以及与这些特征相关的属性信息。KML文件可以包含地理坐标、颜色、高度、图标等元素,使其能够用于在地图上呈现丰富的地理信息。)步骤如下:
参考链接(参考代码)
准备一些航迹点,保存为csv文件,格式如下:
pointname | lon | lat | height | heading | gimbal | speed | turnmode | actions_sequence |
point1 | 114.3557 | 30.52796 | 8 | 180 | -1 | 3 | AUTO | H1000*SHOOT |
point2 | 114.3557 | 30.52796 | 8 | 160 | -1 | 3 | AUTO | H1000*SHOOT |
point3 | 114.3557 | 30.52795 | 8 | 140 | -1 | 3 | AUTO | H1000*SHOOT |
point4 | 114.3558 | 30.52794 | 8 | 120 | -1 | 3 | AUTO | H1000*SHOOT |
point5 | 114.3558 | 30.52793 | 8 | 100 | -1 | 3 | AUTO | H1000*SHOOT |
point6 | 114.3558 | 30.52791 | 8 | 80 | -1 | 3 | AUTO | H1000*SHOOT |
point7 | 114.3558 | 30.5279 | 8 | 60 | -1 | 3 | AUTO | H1000*SHOOT |
point8 | 114.3557 | 30.52788 | 8 | 40 | -1 | 3 | AUTO | H1000*SHOOT |
其中列分别是:点名、经度、维度、海拔高度、偏航角(注意所使用的大疆软件偏航角是否可以为小数,DJI Pilot软件不支持偏航角为小数,如果偏航角是小数在DJI Pilot中为0!),云台俯仰角,无人机飞行的速度,转弯模式(可能的值包括"MANUAL"、"AUTO"),在航迹点执行的动作序列(可以没有任何动作,也可以有一个或多个动作。例如表格中动作为悬停1s并拍照)
- H1000 => 悬停 1000ms = 1s
- 拍摄 => 拍照
- G40 =>万向架 a -40°
- REC => 开始视频录制
- STOPREC => 停止录像
- A-170 => 将飞机转向 -170 。航向的范围为 [-180, 180] 度,其中 0 代表正北。
3. 利用Python脚本将csv转化为kml文件
from string import Template
import csv
import sys
import argparse
import xml.etree.ElementTree as ET
import simplekml
from pyproj import CRS, Transformer
import pandas as pd
# csv to kml
def generate_kml(csv_file, output_file, on_finish='hover'):
CSV_HEADER = False
ON_FINISH = "Hover" if on_finish == 'hover' else "GoHome"
XML_string = """<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2">
<Document xmlns="">
<name>chambon_small</name>
<open>1</open>
<ExtendedData xmlns:mis="www.dji.com">
<mis:type>Waypoint</mis:type>
<mis:stationType>0</mis:stationType>
</ExtendedData>
<Style id="waylineGreenPoly">
<LineStyle>
<color>FF0AEE8B</color>
<width>6</width>
</LineStyle>
</Style>
<Style id="waypointStyle">
<IconStyle>
<Icon>
<href>https://cdnen.dji-flighthub.com/static/app/images/point.png</href>
</Icon>
</IconStyle>
</Style>
<Folder>
<name>Waypoints</name>
<description>Waypoints in the Mission.</description>\n"""
all_coordinates = ""
waypoint_number = 1
waypoint_start = Template(""" <Placemark>
<name>Waypoint$waypoint_number</name>
<visibility>1</visibility>
<description>Waypoint</description>
<styleUrl>#waypointStyle</styleUrl>
<ExtendedData xmlns:mis="www.dji.com">
<mis:useWaylineAltitude>false</mis:useWaylineAltitude>
<mis:heading>$heading</mis:heading>
<mis:turnMode>$turnmode</mis:turnMode>
<mis:gimbalPitch>$gimbal</mis:gimbalPitch>
<mis:useWaylineSpeed>false</mis:useWaylineSpeed>
<mis:speed>$speed</mis:speed>
<mis:useWaylineHeadingMode>true</mis:useWaylineHeadingMode>
<mis:useWaylinePointType>true</mis:useWaylinePointType>
<mis:pointType>LineStop</mis:pointType>
<mis:cornerRadius>0.2</mis:cornerRadius>""")
waypoint_end = Template("""
</ExtendedData>
<Point>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>$lon,$lat,$height</coordinates>
</Point>
</Placemark>""")
hover_template = Template("""
<mis:actions param="$length" accuracy="0" cameraIndex="0" payloadType="0" payloadIndex="0">Hovering</mis:actions>""")
shoot_template = Template("""
<mis:actions param="0" accuracy="0" cameraIndex="0" payloadType="0" payloadIndex="0">ShootPhoto</mis:actions>""")
gimbal_template = Template("""
<mis:actions param="$gimbal_angle" accuracy="1" cameraIndex="0" payloadType="0" payloadIndex="0">GimbalPitch</mis:actions>""")
aircraftyaw_template = Template("""
<mis:actions param="$aircraftyaw" accuracy="0" cameraIndex="0" payloadType="0" payloadIndex="0">AircraftYaw</mis:actions>""")
record_template = Template("""
<mis:actions param="0" accuracy="0" cameraIndex="0" payloadType="0" payloadIndex="0">StartRecording</mis:actions>""")
stoprecord_template = Template("""
<mis:actions param="0" accuracy="0" cameraIndex="0" payloadType="0" payloadIndex="0">StopRecording</mis:actions>""")
all_coordinates_template = Template("$lon,$lat,$height")
xml_end = Template(""" </Folder>
<Placemark>
<name>Wayline</name>
<description>Wayline</description>
<visibility>1</visibility>
<ExtendedData xmlns:mis="www.dji.com">
<mis:altitude>50.0</mis:altitude>
<mis:autoFlightSpeed>5.0</mis:autoFlightSpeed>
<mis:actionOnFinish>$ON_FINISH</mis:actionOnFinish>
<mis:headingMode>UsePointSetting</mis:headingMode>
<mis:gimbalPitchMode>UsePointSetting</mis:gimbalPitchMode>
<mis:powerSaveMode>false</mis:powerSaveMode>
<mis:waypointType>LineStop</mis:waypointType>
<mis:droneInfo>
<mis:droneType>COMMON</mis:droneType>
<mis:advanceSettings>false</mis:advanceSettings>
<mis:droneCameras/>
<mis:droneHeight>
<mis:useAbsolute>false</mis:useAbsolute>
<mis:hasTakeoffHeight>false</mis:hasTakeoffHeight>
<mis:takeoffHeight>0.0</mis:takeoffHeight>
</mis:droneHeight>
</mis:droneInfo>
</ExtendedData>
<styleUrl>#waylineGreenPoly</styleUrl>
<LineString>
<tessellate>1</tessellate>
<altitudeMode>relativeToGround</altitudeMode>
<coordinates>$all_coordinates</coordinates>
</LineString>
</Placemark>
</Document>
</kml>""")
with open(csv_file, newline='') as csvfile:
if csv.Sniffer().has_header(csvfile.read(1024)):
CSV_HEADER = True
csvfile.seek(0)
dialect = csv.Sniffer().sniff(csvfile.read(1024))
csvfile.seek(0)
csv_lines = csv.reader(csvfile, dialect)
if CSV_HEADER:
next(csv_lines, None) # skip the headers
for row in csv_lines:
if row:
name, lon, lat, height, heading, gimbal, speed, turnmode, actions_sequence = row
if lon[0] == '_':
lon = lon[1:]
if lat[0] == '_':
lat = lat[1:]
if (float(speed) > 15) or (float(speed) <= 0):
sys.exit('speed should be >0 or <=15 m/s for {}'.format(name))
if '.' not in speed:
speed = speed+'.0'
if '.' not in gimbal:
gimbal = gimbal+'.0'
if turnmode == 'AUTO':
turnmode = 'Auto'
elif turnmode == 'C':
turnmode = 'Clockwise'
elif turnmode == 'CC':
turnmode = 'Counterclockwise'
else:
sys.exit('turnmode should be AUTO, C, or CC for {}'.format(name))
XML_string += waypoint_start.substitute(
turnmode=turnmode, waypoint_number=waypoint_number, speed=speed, heading=heading, gimbal=gimbal)
# Actions decoding
if actions_sequence:
action_list = actions_sequence.split('*')
for action in action_list:
if action == 'SHOOT':
XML_string += shoot_template.substitute()
elif action == 'REC':
XML_string += record_template.substitute()
elif action == 'STOPREC':
XML_string += stoprecord_template.substitute()
# Gimbal orientation
elif action[0] == 'G':
XML_string += gimbal_template.substitute(
gimbal_angle=action[1:])
# Aircraft orientation
elif action[0] == 'A':
XML_string += aircraftyaw_template.substitute(
aircraftyaw=action[1:])
elif action[0] == 'H':
if float(action[1:]) < 500:
sys.exit(
'Hover length is in ms and should be >500 for {}'.format(name))
XML_string += hover_template.substitute(
length=action[1:])
XML_string += "\n" + \
waypoint_end.substitute(lon=lon, lat=lat,
height=height,)+"\n"
all_coordinates += all_coordinates_template.substitute(
lon=lon, lat=lat, height=height)+" "
waypoint_number += 1
# remove last space from coordinates string
all_coordinates = all_coordinates[:-1]
XML_string += xml_end.substitute(
all_coordinates=all_coordinates, ON_FINISH=ON_FINISH)
with open(output_file, 'w') as output_file:
output_file.write(XML_string)
#分割csv
def split_csv(input_file, chunk_size=99):
# 读取原始CSV文件
df = pd.read_csv(input_file)
# 获取总行数
total_rows = len(df)
# 计算分割后的文件数
num_files = (total_rows + chunk_size - 1) // chunk_size
filenames = []
# 分割数据并保存为新的CSV文件
for i in range(num_files):
start_idx = i * chunk_size
end_idx = min((i + 1) * chunk_size, total_rows)
# 生成新文件名
output_file = f"{input_file.replace('.csv', '')}_{i + 1}.csv"
filenames.append(output_file)
# 写入分割后的数据到新文件
df.iloc[start_idx:end_idx].to_csv(output_file, index=False)
return filenames
def main():
Split = True
input_csv_filename = 'kml.csv' # 替换为输入 CSV 文件名
output_kml_filename = 'kml.kml' # 替换为输出 KML 文件名
#是否分割
if Split:
filenames = split_csv(input_csv_filename,99)
for i, file in enumerate(filenames, start=1):
generate_kml(file, f"{output_kml_filename.replace('.kml', '')}_{i}.kml")
else:
generate_kml(input_csv_filename, output_kml_filename) #csv转kml
if __name__ == '__main__':
main()
将输入的csv文件进行修改,以及是否进行分割(DJI Pilot中每次飞行不可多于99个点所以我进行了切割)。
4. kml文件制作完成后可导入到大疆的无人机软件中或者Google Earth中查看,例如:
导入到大疆无人机控制软件中后即可进行飞行!注意不同软件可能有不同限制,例如DJI Pilot中每次飞行不可多于99个点且相邻航迹点不可过近,并且偏航角只能是整数。