大疆文档(9)-Android教程-GEO系统App

本节全篇为大疆 Mobile SDK 安卓教程 部分,ios教程参见 IOS教程 .

DJI GEO 系统教程

在本教程中,你将学会如何使用 DJI Mobile SDK 的 FlyZoneManagerFlyZoneInformation 获取飞行区域信息,并解锁授权飞行区域。

你可以从这里下载本教程的最终示例项目: Github Page.

我们使用 Phantom 4 作为这个demo的示例,开始吧!

介绍

Geospatial Environment Online (GEO) system 是一个一流的地理空间信息系统,为无人机操作者提供信息,帮助他们在何时何地飞行做出明智的决定。它结合了最新的空域信息、预警和飞行限制系统、在特定条件下允许飞行的地点 unlocking (自行授权)无人机飞行的机制,以及对这些决策的最低侵入性问责机制。

在中国进行应用激活和飞机绑定

对于在中国使用的DJI SDK移动应用程序,需要激活应用程序并将飞机绑定到用户的DJI帐户。

如果未激活应用程序,未使用飞机(如果需要)或使用旧版SDK(<4.1),则将禁用所有 相机实时流 ,并且飞行将限制为直径100米和高度为30米的区域,以确保飞机保持在视线范围内。

要了解如何实现此功能,请查看本教程 Application Activation and Aircraft Binding.

实现应用程序UI

在我们之前的教程中l Importing and Activating DJI SDK in Android Studio Project,您已经学习了如何导入 Android SDK Maven 依赖项并激活您的应用程序。如果你之前没看过,那就回去看一下相关实现。然后再继续。

导入 Maven 依赖

  • 创建名为 DJIGEODemo 的新项目
  • 包名 com.dji.geodemo
  • 最低版本 API 19: Android 4.4 (KitKat)
  • 选择 "Empty Activity" 然后其他默认

配置 Google Maps API Key

由于我们用 Android Studio 的 "Google Maps Activity" 创建这个demo项目,而google play 服务会自动为你设置它。所以你现在可以开始用 Google Maps Android API 去开发你的app了。

学习如何生成 SHA-1 key 并如何申请 Google Maps API key ,你可以看看这个 Google Developer Console, 请参考 Creating a MapView and Waypoint Application 教程的 Setting Up Google Play Services 部分。

完成上述部分后,打开 "google_maps_api.xml" 文件,并用的申请的 Google Maps API key 替换 YOUR_KEY_HERE ,如下所示:

<string name="google_maps_key" translatable="false" templateMergeStrategy="preserve">YOUR_KEY_HERE</string>

现在构建并运行项目,并安装到你的Android设备,你应该可以看到 Google Map 加载成功后是这样的:

googleMap

构建 Activity 局部

1. 实现 MApplication 和 GEODemoApplication

关于 MApplicationGEODemoApplication的详细实现,你可以看一下这两个教程 Creating an Camera Applicationsample project

2. 实现 ConnectionActivity

在ConnectionActivity类中实现UI元素

要加强用户体验,我们最好在DJI产品和SDK之间创建一个activity来显示连接状态,一旦连接建立,用户可以按 OPEN 按钮来进入 MainActivity

现在,让我们在 com.dji.geodemo 包下创建一个 Basic Activity ,命名为 "ConnectionActivity" ,用以下代码替换:

public class ConnectionActivity extends Activity implements View.OnClickListener {

    private static final String TAG = ConnectionActivity.class.getName();

    private TextView mTextConnectionStatus;
    private TextView mTextProduct;
    private Button mBtnOpen;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_connection);
        initUI();
    }

    @Override
    public void onResume() {
        Log.e(TAG, "onResume");
        super.onResume();
    }

    @Override
    public void onPause() {
        Log.e(TAG, "onPause");
        super.onPause();
    }

    @Override
    public void onStop() {
        Log.e(TAG, "onStop");
        super.onStop();
    }

    public void onReturn(View view){
        Log.e(TAG, "onReturn");
        this.finish();
    }

    @Override
    protected void onDestroy() {
        Log.e(TAG, "onDestroy");
        super.onDestroy();
    }

    private void initUI() {
        mTextConnectionStatus = (TextView) findViewById(R.id.text_connection_status);
        mTextProduct = (TextView) findViewById(R.id.text_product_info);
        mBtnOpen = (Button) findViewById(R.id.btn_open);
        mBtnOpen.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_open: {
                Intent intent = new Intent(this, MainActivity.class);
                startActivity(intent);
                break;
            }
            default:
                break;
        }
    }

}

以上代码实现了以下功能:

  1. 创建了布局的UI元素变量, 包括2个 TextView 和1个 Button
  2. onCreate() 方法中,我们请求了几个运行时权限来保证在编译或目标SDK版本大于22的时候,SDK可以正常运行。 然后调用 initUI() 方法去初始化UI元素。
  3. 接下来,实现 initUI() 方法,初始化3个 TextView 和一个按钮。然后调用 mBtnOpensetOnClickListener() 方法并把 this 作为参数传入。
  4. 最后,重写 onClick() 方法来实现 btn_open 按钮的点击操作。在这里,我们通过传入 "MainActivity.class" 类创建一个 Intent 对象。然后调用 startActivity() 方法启动 MainActivity 。
实现 ConnectionActivity 布局

打开 activity_connection.xml 布局文件,并用如下代码替换:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <TextView
        android:id="@+id/text_connection_status"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Status: No Product Connected"
        android:textColor="@android:color/black"
        android:textSize="20dp"
        android:textStyle="bold"
        android:layout_alignBottom="@+id/text_product_info"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="89dp" />

    <TextView
        android:id="@+id/text_product_info"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="270dp"
        android:text="@string/product_information"
        android:textColor="@android:color/black"
        android:textSize="20dp"
        android:gravity="center"
        android:textStyle="bold"
        />

    <Button
        android:id="@+id/btn_open"
        android:layout_width="150dp"
        android:layout_height="55dp"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="350dp"
        android:background="@drawable/round_btn"
        android:text="Open"
        android:textColor="@color/colorWhite"
        android:textSize="20dp"
        />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="430dp"
        android:text="@string/sdk_version"
        android:textSize="15dp"
        android:id="@+id/textView2" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="?android:attr/textAppearanceSmall"
        android:text="DJIGEODemo"
        android:id="@+id/textView"
        android:layout_marginTop="58dp"
        android:textStyle="bold"
        android:textSize="20dp"
        android:textColor="@color/colorBlack"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true" />

</RelativeLayout>

在这个xml文件中,我们在 RelativeLayout 中创建了 4个TextView 和 1个Button 。 我们用 TextView(id: text_connection_status) 来显示产品连接状态,并用 TextView(id:text_product_info) 来显示产品名称。 Button(id: btn_open) 用来打开 MainActivity

配置资源文件

完成上述步骤后,我们从 Github 示例项目的 drawable 文件夹中把所有的图片文件都复制到你的项目中。

imageFiles

打开 "colors.xml" 文件,并用以下内容替换:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>

    <color name="title_dark">#212121</color>
    <color name="white">#FFFFFF</color>
    <color name="button_normal">#50808080</color>
    <color name="button_press">#5086BFFF</color>
    <color name="colorWhite">#FFFFFF</color>
    <color name="colorBlack">#000000</color>
    <color name="gs_home_stroke">#b200ae39</color>
    <color name="gs_home_fill">#0d1eff00</color>
    <color name="auth_fill">#28FFFF00</color>
    <color name="limit_fill">#78FF0000</color>
</resources>

打开 "strings.xml" 文件,并用以下内容替换:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">DJIGEODemo</string>

    <string name="title_activity_GEODemo">GEODemo</string>
    <string name="action_settings">Settings</string>
    <string name="disconnected">Disconnected</string>
    <string name="product_information">Product Information</string>
    <string name="connection_loose">Status: No Product Connected</string>
    <string name="sdk_version">DJI SDK Version: %1$s</string>

</resources>

最后,打开 "styles.xml" 文件并用以下内容替换(对于 AS 3.3.2 以下是默认生成的,可以不动):

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

现在,如果你打开 activity_main.xml 文件,并点击左下角的 Design ,你看到的 ConnectionActivity 的预览截图应该是这样的:

ConnectionActivity

3. 创建 MainActivity

实现 MainActivity 布局

打开 activity_main.xml 布局文件,并用以下代码替换:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.dji.geodemo.MainActivity">

    <RelativeLayout
        android:id="@+id/main_title_rl"
        android:layout_width="fill_parent"
        android:layout_height="40dp"
        android:background="@color/title_dark">

        <ImageButton
            android:id="@+id/ReturnBtnCamera"
            android:layout_width="wrap_content"
            android:layout_height="35dp"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:layout_marginLeft="5dp"
            android:adjustViewBounds="true"
            android:background="@android:color/transparent"
            android:onClick="onReturn"
            android:scaleType="centerInside"
            android:src="@drawable/selector_back_button" />

        <TextView
            android:id="@+id/ConnectStatusTextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:text="GEODemo"
            android:textColor="@android:color/white"
            android:textSize="21sp" />
    </RelativeLayout>

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <fragment xmlns:android="http://schemas.android.com/apk/res/android"
            xmlns:tools="http://schemas.android.com/tools"
            xmlns:map="http://schemas.android.com/apk/res-auto"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/map"
            tools:context="com.dji.myapplication.MainActivity"
            android:name="com.google.android.gms.maps.SupportMapFragment"/>

        <ScrollView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentStart="true"
            android:layout_centerVertical="true"
            android:scrollbars="vertical">

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:orientation="vertical">

                <Button
                    android:id="@+id/geo_login_btn"
                    style="@style/left_button_list_button"
                    android:text="@string/geo_login"/>

                <Button
                    android:id="@+id/geo_logout_btn"
                    style="@style/left_button_list_button"
                    android:text="@string/geo_logout"/>

                <Button
                    android:id="@+id/geo_unlock_nfzs_btn"
                    style="@style/left_button_list_button"
                    android:text="@string/geo_unlock_nfzs"/>

                <Button
                    style="@style/left_button_list_button"
                    android:id="@+id/geo_get_unlock_nfzs_btn"
                    android:text="@string/geo_get_unlock_nfzs"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent" />

                <Button
                    style="@style/left_button_list_button"
                    android:id="@+id/geo_get_surrounding_nfz_btn"
                    android:text="@string/geo_get_surrounding_nfz"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content" />

                <Button
                    android:id="@+id/geo_update_location_btn"
                    style="@style/left_button_list_button"
                    android:text="@string/geo_update_location" />

            </LinearLayout>
        </ScrollView>

        <TextView
            android:layout_width="180dp"
            android:layout_height="wrap_content"
            android:id="@+id/login_status"
            android:textSize="14sp"
            android:textColor="@android:color/black"
            android:layout_alignParentRight="true"
            android:layout_marginRight="0dp"
            android:textAlignment="center" />

        <ScrollView
            android:layout_width="180dp"
            android:layout_height="400dp"
            android:layout_alignParentRight="true"
            android:scrollbars="vertical"
            android:layout_below="@+id/login_status">
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@android:color/black"
                android:id="@+id/fly_zone_tv"
                />
        </ScrollView>
    </RelativeLayout>

</LinearLayout>

在这个xml文件中,我们实现了以下UI:

  1. 在顶部创建了一个 RelativeLayout, 用来添加一个返回按钮和一个显示SDK连接状态的 TextView。
  2. 然后再创建一个 RelativeLayout,并在左边添加一个 scrollView ,从上到下有8个按钮: "Login", "Logout", "Unlock NFZs", "Get Unlock NFZs", "Get Surrounding NFZ" 和 "Update Location" , 将它们垂直放置。
  3. 最后,在右边,我们添加一个 TextView 来显示登陆状态和一个带有 textView 的 scrollView 用来显示飞行区域信息。

接下来,打开 "values" 文件夹中的 styles.xml 文件,并在 "AppTheme" style 下面添加以下代码:

<style name="left_button_list_button">
    <item name="android:minHeight">@dimen/left_button_list_button_height</item>
    <item name="android:layout_width">@dimen/left_button_list_button_width</item>
    <item name="android:layout_height">wrap_content</item>
    <item name="android:paddingLeft">@dimen/left_button_list_button_padding_left</item>
    <item name="android:paddingRight">@dimen/left_button_list_button_padding_right</item>
    <item name="android:layout_marginLeft">@dimen/left_button_list_button_margin_left</item>
    <item name="android:layout_marginTop">@dimen/left_button_list_button_margin_top</item>
    <item name="android:background">@drawable/selector_button</item>
    <item name="android:textSize">@dimen/left_button_list_button_text_size</item>
    <item name="android:textColor">@color/white</item>
</style>

此外,在文件夹下创建一个名为 "dimens.xml" 的xml文件,并用以下代码替换:

<resources>
    <!-- Default screen margins, per the Android Design guidelines. -->
    <dimen name="activity_horizontal_margin">16dp</dimen>
    <dimen name="activity_vertical_margin">16dp</dimen>

    <!-- left button list -->
    <dimen name="left_button_list_button_width">150dp</dimen>
    <dimen name="left_button_list_button_height">45dp</dimen>
    <dimen name="left_button_list_button_padding_left">5dp</dimen>
    <dimen name="left_button_list_button_padding_right">5dp</dimen>
    <dimen name="left_button_list_button_margin_left">10dp</dimen>
    <dimen name="left_button_list_button_margin_top">10dp</dimen>
    <dimen name="left_button_list_button_text_size">14sp</dimen>

</resources>

最后,打开 "strings.xml" 文件,并用以下代码替换:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="app_name">DJIGEODemo</string>

    <string name="title_activity_GEODemo">GEODemo</string>
    <string name="action_settings">Settings</string>
    <string name="disconnected">Disconnected</string>
    <string name="product_information">Product Information</string>
    <string name="connection_loose">Status: No Product Connected</string>
    <string name="sdk_version">DJI SDK Version: %1$s</string>


    <!-- GEO with map testing activity-->
    <string name="title_activity_geo_with_map_testing">GeoWithMapTestingActivity</string>
    <string name="demo_desc_geo_with_map">Geo System With Map</string>

    <string name="item_top_left_title">Category</string>
    <string name="item_top_title">EventName</string>
    <string name="Message_WaitStatus">Upload Status</string>

    <string name="title_activity_geo_testing">GeoTestingActivity</string>
    <string name="demo_desc_geo">Geo System</string>
    <string name="geo_get_surrounding_nfz">Get Surrounding NFZ</string>
    <string name="geo_get_location_nfz">Get location NFZ</string>
    <string name="geo_get_unlock_nfz">Get Unlock NFZ</string>
    <string name="geo_refresh_nfz">Refresh Local Data</string>
    <string name="geo_unlock_nfzs">Unlock NFZs</string>
    <string name="geo_get_unlock_nfzs">Get Unlock NFZs</string>
    <string name="geo_reload_unlocked_zone_groups_from_server">Reload Unlocked Zone Groups from Server</string>
    <string name="geo_sync_unlocked_zone_group_to_aircraft">Sync Unlocked Zone Group to Aircraft</string>
    <string name="geo_get_loaded_unlocked_zone_groups">Get Loaded Unlocked Zone Groups</string>
    <string name="geo_login">Login</string>
    <string name="geo_logout">Logout</string>
    <string name="enabled_geo">Enabled GEO</string>
    <string name="start_simulator">Start Simulator</string>
    <string name="stop_simulator">Stop Simulator</string>
    <string name="geo_set_geo_enabled">Set GEO Enabled</string>
    <string name="geo_get_geo_enabled">Get GEO Enabled</string>
    <string name="flysafe_clear_text">Clear Text</string>
    <string name="geo_update_location">Update Location</string>
    <string name="geo_load_custom_unlock_zones">Load Custom Unlock Zones from server</string>
    <string name="geo_get_custom_unlock_zones">Get Custom Unlock Zones</string>
    <string name="geo_enable_custom_unlock_zone">Enable Custom Unlock Zone</string>
    <string name="geo_disable_custom_unlock_zone">Disable Custom Unlock Zone</string>
    <string name="geo_get_enabled_custom_unlock_zone">Get Enabled Custom Unlock Zone</string>
    <string name="title_activity_test">TestActivity</string>

</resources>

现在,如果你打开 "activity_maps.xml" 文件,并点击左下角的 Design ,你应该看到的 MainActivity 预览截图如下:

MainActivity

在 MainActivity 类中实现UI元素

让我们返回到 MainActivity.java 类中,并用以下代码替换,记得按照 Android Studio 的建议导入相关类 :

public class MainActivity extends FragmentActivity implements View.OnClickListener, OnMapReadyCallback {

    private static final String TAG = MainActivity.class.getName();

    private GoogleMap map;

    protected TextView mConnectStatusTextView;
    private Button btnLogin;
    private Button btnLogout;
    private Button btnUnlock;
    private Button btnGetUnlock;
    private Button btnGetSurroundNFZ;
    private Button btnUpdateLocation;

    private TextView loginStatusTv;
    private TextView flyZonesTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initUI();

        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
    }

    private void initUI() {

        mConnectStatusTextView = (TextView) findViewById(R.id.ConnectStatusTextView);

        btnLogin = (Button) findViewById(R.id.geo_login_btn);
        btnLogout = (Button) findViewById(R.id.geo_logout_btn);
        btnUnlock = (Button) findViewById(R.id.geo_unlock_nfzs_btn);
        btnGetUnlock = (Button) findViewById(R.id.geo_get_unlock_nfzs_btn);
        btnGetSurroundNFZ = (Button) findViewById(R.id.geo_get_surrounding_nfz_btn);
        btnUpdateLocation = (Button) findViewById(R.id.geo_update_location_btn);

        loginStatusTv = (TextView) findViewById(R.id.login_status);
        loginStatusTv.setTextColor(Color.BLACK);
        flyZonesTv = (TextView) findViewById(R.id.fly_zone_tv);
        flyZonesTv.setTextColor(Color.BLACK);

        btnLogin.setOnClickListener(this);
        btnLogout.setOnClickListener(this);
        btnUnlock.setOnClickListener(this);
        btnGetUnlock.setOnClickListener(this);
        btnGetSurroundNFZ.setOnClickListener(this);
        btnUpdateLocation.setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.geo_login_btn:
                break;

            case R.id.geo_logout_btn:
                break;

            case R.id.geo_unlock_nfzs_btn:
                break;

            case R.id.geo_get_unlock_nfzs_btn:
                break;

            case R.id.geo_get_surrounding_nfz_btn:
                break;

            case R.id.geo_update_location_btn:
                break;

        }
    }

    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    @Override
    public void onMapReady(GoogleMap googleMap) {
        mMap = googleMap;

        // Add a marker in Shenzhen and move the camera
        LatLng shenzhen = new LatLng(22.537018, 113.953640);
        mMap.addMarker(new MarkerOptions().position(shenzhen).title("Marker in shenzhen"));
        mMap.moveCamera(CameraUpdateFactory.newLatLng(shenzhen));
    }
}

以上代码实现了以下功能:

1.onCreate() 方法中, 我们调用了 initUI() 方法,并创建了 "SupportMapFragment" 变量,用来异步调用 OnMapReady()

2. 我们为UI元素创建了一个 GoogleMap 变量, 6个 Button 和2个 TextView 变量 , 然后在 initUI() 方法去初始化UI元素,并实现它们的 setOnClickListener 方法并传入 "this" 参数。

3. 接下来,我们重写6个按钮的 onClick() 方法。

4. 最后,我们重写 onMapReady() 方法来初始化 mMap。然后添加一个 Palo Alto, California (加州帕罗奥多市) 的 marker。因此,当谷歌地图被加载时,您将在 Palo Alto, California 看到一个红色标记。

我们已经花了很长时间来设置程序的UI。现在让我们来构建和运行项目,并安装到你的Android设备上去测试一下吧。 这里大疆用 Nexus 5 进行测试。当程序启动时,按 ConnectionActivity 中的 Open 按钮打开 MainActivity 视图,然后你应该可以看到下图:

p4MissionsUIDemo

注册应用程序

1. 修改 AndroidManifest 文件

完成上述步骤后, 让我们用你在大疆开发者网站上申请的 App Key 来注册我们的应用程序。如果你不熟悉 App Key ,看看这 Get Started.

让我们打开 AndroidManifest.xml 文件,并在 <application> 元素上面添加以下元素:

<!-- SDK permission requirement -->
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_CONFIGURATION" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />

<uses-feature
    android:name="android.hardware.usb.host"
    android:required="false" />
<uses-feature
    android:name="android.hardware.usb.accessory"
    android:required="true" />
<uses-feature
    android:glEsVersion="0x00020000"
    android:required="true" />

<!-- SDK requirement permission end -->

在上面的代码中,我们通过添加 元素作为 元素的子元素指定程序所需权限。

此外,因为不是所有的 Android-powered 设备都保证支持 USB 附件和 host API,包括两个元素,声明你的程序使用 "android.hardware.usb.accessory" 和 "android.hardware.usb.host" 功能。

最后,我们需要指定 OpenGL ES version 2 的需求。

更多描述权限的详情,参考这个 https://developers.google.com/maps/documentation/android/config.

然后,让我们用以下内容替换 元素:

<!-- SDK requirement permission end -->
<application
    android:name="com.dji.geodemo.MApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">

    <!-- DJI SDK -->

    <uses-library android:name="com.android.future.usb.accessory" />

    <meta-data
        android:name="com.dji.sdk.API_KEY"
        android:value="Please enter your App Key here." />
    <meta-data
        android:name="com.google.android.geo.API_KEY"
        android:value="@string/google_maps_key" />
    <meta-data
        android:name="com.google.android.gms.version"
        android:value="@integer/google_play_services_version" />

    <activity
        android:name="dji.sdk.sdkmanager.DJIAoaControllerActivity"
        android:screenOrientation="landscape"
        android:theme="@android:style/Theme.Translucent">
        <intent-filter>
            <action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
        </intent-filter>

        <meta-data
            android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
            android:resource="@xml/accessory_filter" />
    </activity>

    <!-- DJI SDK -->

    <activity android:name=".ConnectionActivity"
        android:screenOrientation="portrait">

        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <activity android:name=".MainActivity"
        android:screenOrientation="landscape"></activity>
</application>

请在 android:name="com.dji.sdk.API_KEY" 属性的 value 部分输入应用程序的 App Key。有关 AndroidManifest.xml 文件的更多详情,请查看本教程演示项目的Github源代码。

2. 处理 GEODemoApplication 和 ConnectionAcitity

在 "GEODemoApplication.java" 和 "ConnectionAcitity.java" 文件中实现注册逻辑,这里我们不解释更多的细节。请自行查看本教程的 Github 源代码。

现在让我们构建并运行项目,并安装到你的Android设备上。如果一切ok,当你注册成功的时候,应该可以看到 "Register Success" ,就像下图这样:

registerSuccess

在 MainActivity 中实现 GEO 功能

更新连接状态TextView

让我们打开 MainActivity.java 文件,并添加以下两个方法,当产品连接变更时用来更新 mConnectStatusTextView 内容:

@Override
public void onResume() {
    Log.e(TAG, "onResume");
    super.onResume();
    updateTitleBar();
}

private void updateTitleBar() {
    if (mConnectStatusTextView == null) return;
    boolean ret = false;
    BaseProduct product = GEODemoApplication.getProductInstance();
    if (product != null) {
        if (product.isConnected()) {
            //The product is connected
            MainActivity.this.runOnUiThread(new Runnable() {
                public void run() {
                    mConnectStatusTextView.setText(GEODemoApplication.getProductInstance().getModel() + " Connected");
                }
            });
            ret = true;
        } else {
            if (product instanceof Aircraft) {
                Aircraft aircraft = (Aircraft) product;
                if (aircraft.getRemoteController() != null && aircraft.getRemoteController().isConnected()) {
                    // The product is not connected, but the remote controller is connected
                    MainActivity.this.runOnUiThread(new Runnable() {
                        public void run() {
                            mConnectStatusTextView.setText("only RC Connected");
                        }
                    });
                    ret = true;
                }
            }
        }
    }

    if (!ret) {
        // The product or the remote controller are not connected.

        MainActivity.this.runOnUiThread(new Runnable() {
            public void run() {
                mConnectStatusTextView.setText("Disconnected");
            }
        });
    }
}

处理 Login 和 Logout 功能

现在,让我们在 MainActivity.java 文件中的 initUI() 方法底部添加以下代码:

MainActivity.this.runOnUiThread(new Runnable() {
    public void run() {
        loginStatusTv.setText(loginStatusTv.setText(UserAccountManager.getInstance().getUserAccountState().name());
    }
});

以上代码中,我们调用了 UserAccountManagergetUserAccountState() 方法,来获取当前用户帐户状态,并更新 loginStatusTv 的文本内容。

接下来,我们为 btnLoginbtnLogout 按钮实现 onClick() 方法,如下所示:

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.geo_login_btn:
            UserAccountManager.getInstance().logIntoDJIUserAccount(this,
                    new CommonCallbacks.CompletionCallbackWith<UserAccountState>() {
                        @Override
                        public void onSuccess(final UserAccountState userAccountState) {
                            showToast(userAccountState.name());
                            MainActivity.this.runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    loginStatusTv.setText(userAccountState.name());
                                }
                            });
                        }

                        @Override
                        public void onFailure(DJIError error) {
                            showToast(error.getDescription());
                        }
                    });

            break;

        case R.id.geo_logout_btn:

            UserAccountManager.getInstance().logoutOfDJIUserAccount(new CommonCallbacks.CompletionCallback() {
                @Override
                public void onResult(DJIError error) {
                    if (null == error) {
                        showToast("logoutOfDJIUserAccount Success");
                        MainActivity.this.runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                loginStatusTv.setText("NotLoggedin");
                            }
                        });
                    } else {
                        showToast(error.getDescription());
                    }
                }
            });

            break;
        case R.id.geo_unlock_nfzs_btn:
            break;

        case R.id.geo_get_unlock_nfzs_btn:
            break;

        case R.id.geo_get_surrounding_nfz_btn:
            break;

        case R.id.geo_update_location_btn:
            break;

    }
}

在以上代码中,我们调用了 UserAccountManagerlogIntoDJIUserAccount() 方法为用户提供一个登录视图。当登录成功的时候,我们就更新 loginStatusTv 的内容为用户账户状态。累死的,调用 UserAccountManagerlogoutOfDJIUserAccount() 方法来注销用户账户。

处理 GEO 系统功能

更新 Fly Zone Info 和 Aircraft Location

如果你想解锁一个飞行区域,你可能首先需要获取飞行区域的id。 现在让我们在 flyZonesTv 中更新飞行区域信息,并在模拟坐标数据变更时更新飞机的位置。

onCreate() 方法中创建以下变量:

private Marker marker;
private LatLng latLng;
private double droneLocationLat = 181, droneLocationLng = 181;
private FlightController mFlightController = null;

然后在 onMapReady() 方法上面添加以下几个方法:

private void initFlightController() {

    if (isFlightControllerSupported()) {
        mFlightController = ((Aircraft) DJISDKManager.getInstance().getProduct()).getFlightController();
        mFlightController.setStateCallback(new FlightControllerState.Callback() {
            @Override
            public void onUpdate(FlightControllerState
                                         djiFlightControllerCurrentState) {
                if (mMap != null) {
                    droneLocationLat = djiFlightControllerCurrentState.getAircraftLocation().getLatitude();
                    droneLocationLng = djiFlightControllerCurrentState.getAircraftLocation().getLongitude();
                    updateDroneLocation();
                }
            }
        });
    }
}

public static boolean checkGpsCoordinates(double latitude, double longitude) {
    return (latitude > -90 && latitude < 90 && longitude > -180 && longitude < 180) && (latitude != 0f && longitude != 0f);
}

private void updateDroneLocation(){
    runOnUiThread(new Runnable() {
        @Override
        public void run() {
            if (marker != null) {
                marker.remove();
            }
            if (checkGpsCoordinates(droneLocationLat, droneLocationLng)) {

                LatLng pos = new LatLng(droneLocationLat, droneLocationLng);

                //Create MarkerOptions object
                final MarkerOptions markerOptions = new MarkerOptions();
                markerOptions.position(pos);
                markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.aircraft));
                marker = mMap.addMarker(markerOptions);
            }
        }
    });
}

private boolean isFlightControllerSupported() {
    return DJISDKManager.getInstance().getProduct() != null &&
            DJISDKManager.getInstance().getProduct() instanceof Aircraft &&
            ((Aircraft) DJISDKManager.getInstance().getProduct()).getFlightController() != null;
}

以上代码中,我们主要初始化了 mFlightController 变量,并实现了 FlightControllersetStateCallback() 方法用来调用 updateDroneLocation() 方法,当飞机移动时更新地图上的飞机位置。

此外,让我们来更新 onMapReady() 方法,如下所示:

@Override
public void onMapReady(GoogleMap googleMap) {
    LatLng paloAlto = new LatLng(37.4613697, -122.1237315);

    mMap = googleMap;
    mMap.moveCamera(CameraUpdateFactory.newLatLng(paloAlto));
    mMap.animateCamera(CameraUpdateFactory.zoomTo(17.0f));
    if (ActivityCompat.checkSelfPermission(this, android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
        mMap.setMyLocationEnabled(true);
        return;
    }

    printSurroundFlyZones();
}

以上代码中,我们初始化了 mMap 变量,并调用了 GoogleMapmoveCamera()animateCamera() 方法,用来移动相机和放大地图。然后,调用 printSurroundFlyZones() 方法来更新飞行区域信息。

最后,在 onMapReady() 下面添加以下几个方法:

private void printSurroundFlyZones() {

DJISDKManager.getInstance().getFlyZoneManager().getFlyZonesInSurroundingArea(new CommonCallbacks.CompletionCallbackWith<ArrayList<FlyZoneInformation>>() {
    @Override
    public void onSuccess(ArrayList<FlyZoneInformation> flyZones) {
        showToast("get surrounding Fly Zone Success!");
        updateFlyZonesOnTheMap(flyZones);
        showSurroundFlyZonesInTv(flyZones);
    }

    @Override
    public void onFailure(DJIError error) {
        showToast(error.getDescription());
    }
});
}

private void showSurroundFlyZonesInTv(final List<FlyZoneInformation> flyZones) {
    MainActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            StringBuffer sb = new StringBuffer();
            for (FlyZoneInformation flyZone : flyZones) {
                if (flyZone != null && flyZone.getCategory() != null){

                    sb.append("FlyZoneId: ").append(flyZone.getFlyZoneID()).append("\n");
                    sb.append("Category: ").append(flyZone.getCategory().name()).append("\n");
                    sb.append("Latitude: ").append(flyZone.getCoordinate().getLatitude()).append("\n");
                    sb.append("Longitude: ").append(flyZone.getCoordinate().getLongitude()).append("\n");
                    sb.append("FlyZoneType: ").append(flyZone.getFlyZoneType().name()).append("\n");
                    sb.append("Radius: ").append(flyZone.getRadius()).append("\n");
                    sb.append("Shape: ").append(flyZone.getShape().name()).append("\n");
                    sb.append("StartTime: ").append(flyZone.getStartTime()).append("\n");
                    sb.append("EndTime: ").append(flyZone.getEndTime()).append("\n");
                    sb.append("UnlockStartTime: ").append(flyZone.getUnlockStartTime()).append("\n");
                    sb.append("UnlockEndTime: ").append(flyZone.getUnlockEndTime()).append("\n");
                    sb.append("Name: ").append(flyZone.getName()).append("\n");
                    sb.append("\n");
                }
            }
            flyZonesTv.setText(sb.toString());
        }
    });
}

上面代码中,我们调用了 FlyZoneManagergetFlyZonesInSurroundingArea 方法来获取飞行区域信息列表,然后在 onSuccess() 方法中,调用了 showSurroundFlyZonesInTv() 方法来更新 flyZonesTv 上的飞行区域信息。

最后,让我们来实现 btnGetSurroundNFZbtnUpdateLocation 按钮的 OnClick() 方法。

case R.id.geo_get_surrounding_nfz_btn:
    printSurroundFlyZones();
    break;

case R.id.geo_update_location_btn:
    latLng = new LatLng(DataOsdGetPushCommon.getInstance().getLatitude(),
            DataOsdGetPushCommon.getInstance().getLongitude());
    if (latLng != null) {
        //Create MarkerOptions object
        final MarkerOptions markerOptions = new MarkerOptions();
        markerOptions.position(latLng);
        markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.aircraft));
        marker = mMap.addMarker(markerOptions);
    }
    mMap.moveCamera(CameraUpdateFactory.newLatLng(latLng));
    mMap.animateCamera(CameraUpdateFactory.zoomTo(15.0f));
    break;

当你按下 btnGetSurroundNFZ 按钮时,将会调用 printSurroundFlyZones()flyZonesTv 上的飞行区域信息进行更新。

当你按下 btnUpdateLocation 按钮时,将会从 DataOsdGetPushCommon 中更新后的飞机经纬度数据,并在地图上更新飞机位置。 然后调用 GoogleMapmoveCamera()animateCamera() 方法,将相机移动并放大到地图上飞机的更新位置。

解锁飞行区域

当你完成上述步骤后,让我们在 onCreate() 方法上面创建更多的变量:

private MarkerOptions markerOptions = new MarkerOptions();
private ArrayList<Integer> unlockFlyZoneIds = new ArrayList<Integer>();

接下来,更新 onResume() 方法,并在 onCreate() 方法下面添加以下几个方法:

@Override
public void onResume() {
    Log.e(TAG, "onResume");
    super.onResume();
    updateTitleBar();
    initFlightController();
}

@Override
public void onPause() {
    Log.e(TAG, "onPause");
    super.onPause();
}

@Override
public void onStop() {
    Log.e(TAG, "onStop");
    super.onStop();
}

public void onReturn(View view) {
    Log.e(TAG, "onReturn");
    this.finish();
}

以上代码中,我们主要重写了 Activity 的几种方法(生命周期函数)。 在 onResume() 方法中,我们调用了 initFlightController() 方法来初始化 mFlightController 变量。

接下来,在 onCreate() 方法底部添加以下代码:

DJISDKManager.getInstance().getFlyZoneManager()
        .setFlyZoneStateCallback(new FlyZoneState.Callback() {
            @Override
            public void onUpdate(FlyZoneState status) {
                showToast(status.name());
            }
        });

以上代码将为用户显示禁止飞行状态。 例如,如果飞机接近禁飞区,将会给用户弹出一条警告消息。

在解锁飞行区域之前,我们还应该通过绘制不同颜色的多边形或圆圈在地图上显示飞行区域。 首先在 onCreate() 方法上面定义一个 unlockableIds 列表,两个 int 变量和一个 FlyfrbBasePainter 对象。

private ArrayList<Integer> unlockableIds = new ArrayList<Integer>();
private final int limitFillColor = Color.HSVToColor(120, new float[] {0, 1, 1});
private final int limitCanUnlimitFillColor = Color.argb(40, 0xFF, 0xFF, 0x00);
private FlyfrbBasePainter painter = new FlyfrbBasePainter();

更多 FlyfrbBasePainter 类的详情,请自行查看本教程的 "FlyfrbBasePainter.java" 文件。

然后创建 updateFlyZonesOnTheMap() 方法,并在 printSurroundFlyZones() 方法中调用它:

private void updateFlyZonesOnTheMap(final ArrayList<FlyZoneInformation> flyZones) {
        if (mMap == null) {
            return;
        }
        MainActivity.this.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mMap.clear();
                if (latLng != null) {

                    //Create MarkerOptions object
                    final MarkerOptions markerOptions = new MarkerOptions();
                    markerOptions.position(latLng);
                    markerOptions.icon(BitmapDescriptorFactory.fromResource(R.drawable.aircraft));

                    marker = mMap.addMarker(markerOptions);
                }
                for (FlyZoneInformation flyZone : flyZones) {

                    //print polygon
                    if(flyZone.getSubFlyZones() != null){
                        SubFlyZoneInformation[] polygonItems = flyZone.getSubFlyZones();
                        int itemSize = polygonItems.length;
                        for(int i = 0; i != itemSize; ++i) {
                            if(polygonItems[i].getShape() == SubFlyZoneShape.POLYGON) {
                                DJILog.d("updateFlyZonesOnTheMap", "sub polygon points " + i + " size: " + polygonItems[i].getVertices().size());
                                DJILog.d("updateFlyZonesOnTheMap", "sub polygon points " + i + " category: " + flyZone.getCategory().value());
                                DJILog.d("updateFlyZonesOnTheMap", "sub polygon points " + i + " limit height: " + polygonItems[i].getMaxFlightHeight());
                                addPolygonMarker(polygonItems[i].getVertices(), flyZone.getCategory().value(), polygonItems[i].getMaxFlightHeight());
                            }
                            else if (polygonItems[i].getShape() == SubFlyZoneShape.CYLINDER){
                                LocationCoordinate2D tmpPos = polygonItems[i].getCenter();
                                double subRadius = polygonItems[i].getRadius();
                                DJILog.d("updateFlyZonesOnTheMap", "sub circle points " + i + " coordinate: " + tmpPos.getLatitude() + "," + tmpPos.getLongitude());
                                DJILog.d("updateFlyZonesOnTheMap", "sub circle points " + i + " radius: " + subRadius);

                                CircleOptions circle = new CircleOptions();
                                circle.radius(subRadius);
                                circle.center(new LatLng(tmpPos.getLatitude(),
                                        tmpPos.getLongitude()));
                                switch (flyZone.getCategory()) {
                                    case WARNING:
                                        circle.strokeColor(Color.GREEN);
                                        break;
                                    case ENHANCED_WARNING:
                                        circle.strokeColor(Color.BLUE);
                                        break;
                                    case AUTHORIZATION:
                                        circle.strokeColor(Color.YELLOW);
                                        unlockableIds.add(flyZone.getFlyZoneID());
                                        break;
                                    case RESTRICTED:
                                        circle.strokeColor(Color.RED);
                                        break;

                                    default:
                                        break;
                                }
                                mMap.addCircle(circle);
                            }
                        }
                    }
                    else {
                        CircleOptions circle = new CircleOptions();
                        circle.radius(flyZone.getRadius());
                        circle.center(new LatLng(flyZone.getCoordinate().getLatitude(), flyZone.getCoordinate().getLongitude()));
                        switch (flyZone.getCategory()) {
                            case WARNING:
                                circle.strokeColor(Color.GREEN);
                                break;
                            case ENHANCED_WARNING:
                                circle.strokeColor(Color.BLUE);
                                break;
                            case AUTHORIZATION:
                                circle.strokeColor(Color.YELLOW);
                                unlockableIds.add(flyZone.getFlyZoneID());
                                break;
                            case RESTRICTED:
                                circle.strokeColor(Color.RED);
                                break;

                            default:
                                break;
                        }
                        mMap.addCircle(circle);
                    }
                }

            }
        });

    }

private void addPolygonMarker(List<LocationCoordinate2D> polygonPoints, int area_level, int height) {
    if(polygonPoints == null) {
        return;
    }

    ArrayList<LatLng> points = new ArrayList<>();

    for (LocationCoordinate2D point : polygonPoints) {
        points.add(new LatLng(point.getLatitude(), point.getLongitude()));
    }
    int fillColor = limitFillColor;
    if(painter.getmHeightToColor().get(height) != null) {
        fillColor = painter.getmHeightToColor().get(height);
    }
    else if(area_level == FlyForbidProtocol.LevelType.CAN_UNLIMIT.value()) {
        fillColor = limitCanUnlimitFillColor;
    } else if(area_level == FlyForbidProtocol.LevelType.STRONG_WARNING.value() || area_level == FlyForbidProtocol.LevelType.WARNING.value()) {
        fillColor = getResources().getColor(R.color.gs_home_fill);
    }
    Polygon plg = mMap.addPolygon(new PolygonOptions().addAll(points)
            .strokeColor(painter.getmColorTransparent())
            .fillColor(fillColor));

}

private void printSurroundFlyZones() {

    DJISDKManager.getInstance().getFlyZoneManager().getFlyZonesInSurroundingArea(new CommonCallbacks.CompletionCallbackWith<ArrayList<FlyZoneInformation>>() {
        @Override
        public void onSuccess(ArrayList<FlyZoneInformation> flyZones) {
            showToast("get surrounding Fly Zone Success!");
            updateFlyZonesOnTheMap(flyZones);
            showSurroundFlyZonesInTv(flyZones);
        }

        @Override
        public void onFailure(DJIError error) {
            showToast(error.getDescription());
        }
    });
}

以上代码实现了以下功能:

  1. updateFlyZonesOnTheMap() 方法中,我们用for循环从飞行区域列表中获取每个 flyZone 对象,然后检查 flyZone 是否有多边形飞行区域,并在地图上用不同的颜色添加不同形状的飞行区域。

  2. 接下来,在 printSurroundFlyZones() 方法中,我们调用 FlyZoneManagergetFlyZonesInSurroundingArea 方法获取周边区域的飞行区域信息。然后调用 updateFlyZonesOnTheMap() 方法,并通过 flyZones 对象在地图上绘制飞行区域形状。另外,调用 showSurroundFlyZonesInTv() 方法在 flyZonesTv 上显示飞行区域信息。

最后,让我们来实现 btnUnlockbtnGetUnlock 按钮的 onClick() 方法,如下所示:

case R.id.geo_unlock_nfzs_btn:

    final AlertDialog.Builder builder = new AlertDialog.Builder(this);
    final EditText input = new EditText(this);
    input.setHint("Enter Fly Zone ID");
    input.setInputType(EditorInfo.TYPE_CLASS_NUMBER);
    builder.setView(input);
    builder.setTitle("Unlock Fly Zones");
    builder.setItems(new CharSequence[]
                    {"Continue", "Unlock", "Cancel"},
            new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    // The 'which' argument contains the index position
                    // of the selected item
                    switch (which) {
                        case 0:
                            if (TextUtils.isEmpty(input.getText())) {
                                dialog.dismiss();
                            } else {
                                String value1 = input.getText().toString();
                                unlockFlyZoneIds.add(Integer.parseInt(value1));
                            }
                            break;
                        case 1:
                            if (TextUtils.isEmpty(input.getText())) {
                                dialog.dismiss();
                            } else {
                                String value2 = input.getText().toString();
                                unlockFlyZoneIds.add(Integer.parseInt(value2));
                                DJISDKManager.getInstance().getFlyZoneManager().unlockFlyZones(unlockFlyZoneIds, new CommonCallbacks.CompletionCallback() {
                                    @Override
                                    public void onResult(DJIError error) {

                                        unlockFlyZoneIds.clear();
                                        if (error == null) {
                                            showToast("unlock NFZ Success!");
                                        } else {
                                            showToast(error.getDescription());
                                        }
                                    }
                                });
                            }
                            break;
                        case 2:
                            dialog.dismiss();
                            break;
                    }
                }
            });

    builder.show();
    break;

case R.id.geo_get_unlock_nfzs_btn:

    DJISDKManager.getInstance().getFlyZoneManager().getUnlockedFlyZones(new CommonCallbacks.CompletionCallbackWith<List<FlyZoneInformation>>(){
        @Override
        public void onSuccess(final List<FlyZoneInformation> flyZoneInformations) {
            showToast("Get Unlock NFZ success");
            showSurroundFlyZonesInTv(flyZoneInformations);
        }

        @Override
        public void onFailure(DJIError djiError) {
            showToast(djiError.getDescription());
        }
    });

    break;

以上代码实现了以下功能:

1. 对于 btnUnlock 按钮, 我们创建了一个标题为 "Unlock Fly Zone ID" 的 "AlertDialog" ,并添加一个提示为 "Enter Fly Zone ID" 的EditText。然后为 Continue, Unlock, 和 Cancel 操作创建3个item:

  • Continue

把当前输入的飞行区域ID添加到 unlockFlyZoneIds 数组列表中,然后关闭 "AlertDialog"。

  • Unlock

把当前输入的飞行区域ID添加到 unlockFlyZoneIds 数组列表中,并通过传递 unlockFlyZoneIds 数组作为参数来调用 FlyZoneManagerunlockFlyZones() 方法,用来解锁飞行区域。

  • Cancel

关闭 "AlertDialog"

2. 对于 btnGetUnlock 按钮,我们调用 FlyZoneManagergetUnlockedFlyZones() 方法来获取解锁飞行区域信息,然后重写 onSuccess() 方法,并通过传递 flyZoneInformations 数组列表作为参数来调用 showSurroundFlyZonesInTv() 方法,用来在右边的 flyZonesTv 上更新飞行区域信息。

关于更多的实现,请查看本教程的Github示例程序。

运行示例代码

我们目前已经走了很长的路了,现在,让我们来构建并运行项目,把demo程序连接到你的 Phantom 4 上 (详情看这个 Run Application ) ,并看一下我们目前实现的所有功能。

解锁授权飞行区域工作流

1. 登录您的验证大疆帐户,如果是新帐户,您需要完成验证过程。

2. 打开 DJI Assistant 2 模拟器或PC模拟器,并输入坐标数据 (37.4613697, -122.1237315) (帕洛阿尔托机场附近) ,开始模拟飞机到授权区域的坐标。

3. 按下 UPDATE LOCATIONGET SURROUNDING NFZ 按钮更新地图上的飞机位置,并在右边的 TextView 上更新飞机周围的飞行区域信息。

4. 从 textView 中获取要解锁的授权飞行区域ID,它的类别应该是 Authorization

5.UNLOCK NFZS 按钮并输入飞行区域ID可以解锁。

6. 如果解锁飞行区域成功, 你可以按 GET SURROUNDING NFZ 按钮刷新右边 TextView 上的飞行区域信息,你可能会注意到地图上的一个黄色圆圈将会消失。然后你现在可以在模拟器上起飞了。

Note: 限制模拟区

目前,您只能在 (37.453671, -122.118101) 坐标的 50km 范围内测试GEO功能,这是美国加利福尼亚州 帕洛阿尔托机场 的位置。

登录注销 DJI Account

1. 登录 DJI Account

LOGIN 按钮后会弹出一个登录视图:

login

如果是一个新账户,将显示一个验证视图:

verificationView

2. 注销 DJI Account

LOGOUT 按钮可以注销你的 DJI account.

在截图的右上角,你能看到 loginStatusTv 的用户账户状态信息:

accountStatus

用大疆模拟器模拟飞机位置

我们将使用大疆模拟器去模拟测试环境来定位飞机到特定的经纬度坐标。

如果你用 Phantom 4 测试,请先看看 DJI Assistant 2 Simulator 教程,另外,如果你用的 Phantom 3 Professional,Inspire 1 之类的,那你看看这个教程 DJI PC Simulator

打开 DJI Assistant 2 模拟器或PC模拟器,并输入坐标数据 (37.4613697, -122.1237315) (帕洛阿尔托机场附近) ,开始模拟飞机到授权区域的坐标。

UPDATE LOCATIONGET SURROUNDING NFZ 按钮可以在地图上更新飞机的位置,并在右边的 TextView 上更新飞机周围的飞行区域信息。

等一会儿,你就能看到一个红色的飞机在黄圈里,这是一个可以解锁的授权飞行区域:

updateFlyZone

此外,右边的 textView 将显示 FlyZoneInformation 信息,包括飞行区域名称、飞行区id(解锁过程中需要)、飞行区类别、类型等。

以下是三个飞行区域圆圈的解释:

  • 绿圈

它表示警告飞行区域,不限制飞行,但是有警告用户的信息。 在警告飞行区域中,应该用描述该区域的警告消息提示用户。

  • 黄圈

它表示授权飞行区域,默认限制飞行,GEO验证用户可以解锁。

  • 红圈

它表示限制飞行区域,默认先飞,不能解锁。

解锁并获取解锁飞行区域

1. 解锁飞行区

当您登录您的DJI账号,把飞机定位到 (37.4613697,-122.1237315) 的坐标点,您可以按 UNLOCK NFZS 按钮,并输入飞行区域ID进行解锁。

如果你解锁飞行区域成功,您可以按下 GET SURROUNDING NFZ 按钮来刷新右边的 TextView 上的飞行区域信息,其中一个黄圈将在地图上消失:

unlockFlyZone

2. 获取解锁飞行区列表

你可以按 GET SURROUNDING NFZ 按钮,在右边的 TextView 中获取你之前解锁的飞行区:

getUnlockFlyZones

最后,请重新启动飞机,使设置生效。

摘要

在本教程中,你已经学习了如何使用 DJI Mobile SDK 的 FlyZoneManagerFlyZoneInformation 来获取飞行区域信息,如何解锁授权飞行区和如何在地图上添加飞机注释并绘制飞行区域圈来表示飞行区域。 此外,你还学习了如何使用 DJISimulator 去模拟飞机的坐标,并在不需要在室外飞行的情况下在室内测试GEO系统功能。

希望本教程可以帮你在基于DJI SDK的应用程序中集成 GEO 系统功能。 Good luck!

About This Page

Made with bootstrap and jquery by TaoYuan.

Contact Me

Click! If you are interested