If you come across any mistakes or bugs in this tutorial, please let us know by sending emails to dev@dji.com. Please feel free to send us Github pull request and help us fix any issues.
In this tutorial, you will learn how to use the FlyZoneManager and FlyZoneInformation of DJI Mobile SDK to get the fly zone information, and unlock authorization fly zones.
You can download the tutorial's final sample project from this Github Page.
We use Phantom 4 as an example to make this demo. Let's get started!
Introduction
The Geospatial Environment Online (GEO) system is a best-in-class geospatial information system that provides drone operators with information that will help them make smart decisions about where and when to fly. It combines up-to-date airspace information, a warning and flight-restriction system, a mechanism for unlocking (self-authorizing) drone flights in locations where flight is permitted under certain conditions, and a minimally-invasive accountability mechanism for these decisions.
Application Activation and Aircraft Binding in China
For DJI SDK mobile application used in China, it's required to activate the application and bind the aircraft to the user's DJI account.
If an application is not activated, the aircraft not bound (if required), or a legacy version of the SDK (< 4.1) is being used, all camera live streams will be disabled, and flight will be limited to a zone of 100m diameter and 30m height to ensure the aircraft stays within line of sight.
In the Importing and Activating DJI SDK in Android Studio Project tutorial, you have learned how to import the DJI Android SDK into your Android Studio project and activate your application. If you haven't read that previously, please take a look at it. Once you've done that, let's continue to create the project.
Importing Maven Dependency
Open Android Studio and select File -> New -> New Project to create a new project, named 'DJIGEODemo'. Enter the company domain and package name (Here we use "com.dji.geodemo") you want and press Next. Set the minimum SDK version as API 19: Android 4.4 (KitKat) for "Phone and Tablet" and press Next. Then select "Empty Activity" and press Next. Lastly, leave the Activity Name as "MainActivity", and the Layout Name as "activity_main", Press "Finish" to create the project.
In our previous tutorial Importing and Activating DJI SDK in Android Studio Project, you have learned how to import the Android SDK Maven Dependency and activate your application. If you haven't read that previously, please take a look at it and implement the related features. Once you've done that, continue to implement the next features.
Configurating Google Maps API Key
Since we create this demo project using "Google Maps Activity" of Android Studio, the Google Play Services is set up automatically for you. You can now start using the Google Maps Android APIs to develop your app.
Once you finish the above step, please open the "google_maps_api.xml" file in the values folder and replace the YOUR_KEY_HERE with the Google Maps API key you just get as shown below:
Now build and run the project and install it on an Android device(We use Nexus 5 here), you should see the Google Map loads successfully as shown below:
Building the Layouts of Activity
1. Implementing MApplication and GEODemoApplication
Implementing UI Elements in ConnectionActivity Class
To improve the user experience, we had better create an activity to show the connection status between the DJI Product and the SDK, once it's connected, the user can press the OPEN button to enter the MainActivity.
Now let's Right-click on the package com.dji.geodemo in the project navigator and choose New -> Activity -> Basic Activity, Type in "ConnectionActivity" in the "Activity Name" field and press "Finish" button.
Next, replace the code of the "ConnectionActivity.java" file with the following:
In the code shown above, we implement the following features:
Create the layout UI elements variables, including two TextureViews mTextConnectionStatus, mTextProduct, and one Button mBtnOpen.
In the onCreate() method, we request several permissions at runtime to ensure the SDK works well when the compile and target SDK version is higher than 22(Like Android Marshmallow 6.0 device and API 23). Then invoke the initUI() methods to initialize the UI elements.
Next, implement the initUI() method to initialize the three TextViews and the Button. Then invoke setOnClickListener() method of mBtnOpen and pass this as the param.
Lastly, override the onClick() method to implement the btn_open button action. Here we create an Intent object by passing "MainActivity.class" as the component class to be used for the intent. Then invoke the startActivity() method to start the MainActivity.
Implementing the ConnectionActivity Layout
Open the activity_connection.xml layout file and replace the code with the following:
In the xml file, we create four TextViews and one Button within a RelativeLayout. We use the TextView(id: text_connection_status) to show the product connection status and use the TextView(id:text_product_info) to show the connected product name. The Button(id: btn_open) is used to open the MainActivity.
Configuring the Resource XMLs
Once you finish the above steps, let's copy all the images file from this Github sample project's drawable folder (app->src->main->res->drawable) to the same folder in your project.
Moreover, open the "colors.xml" file and update the content as shown below:
Lastly, open the "styles.xml" file and replace the content with the followings:
<resources>
<!-- Base application theme. --> <stylename="AppTheme"parent="Theme.AppCompat.Light.DarkActionBar"> <!-- Customize your theme here. --> <itemname="colorPrimary">@color/colorPrimary</item> <itemname="colorPrimaryDark">@color/colorPrimaryDark</item> <itemname="colorAccent">@color/colorAccent</item> </style>
</resources>
Now, if you open the activity_main.xml file, and click on the Design tab on the bottom left, you should see the preview screenshot of ConnectionActivity as shown below:
3. Creating the MainActivity
Implementing the MainActivity Layout
Open the activity_main.xml layout file and replace the code with the following:
Create a RelativeLayout to add a back button and a TextView to show the SDK connection status on the top.
Then create a RelativeLayout and add a scrollView on the left side with eight Buttons from top to bottom: "Login", "Logout", "Unlock NFZs", "Get Unlock NFZs", "Get Surrounding NFZ" and "Update Location", place them vertically.
Lastly, on the right side, we add a TextView to show the login status and a scrollView with a textView inside to show the fly zone infos.
Next, open the styles.xml file in the "values" folder and add the following code below the "AppTheme" style:
<!-- GEO with map testing activity--> <stringname="title_activity_geo_with_map_testing">GeoWithMapTestingActivity</string> <stringname="demo_desc_geo_with_map">Geo System With Map</string>
Now, if you open the "activity_maps.xml" file, and click on the Design tab on the bottom left, you should see the preview screenshot of MainActivity as shown below:
Implementing UI Elements in MainActivity Class
Let's come back to the MainActivity.java class, and replace the code with the following, remember to import the related classes as Android Studio suggested:
// 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); }
@Override publicvoidonClick(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 publicvoidonMapReady(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)); } }
In the code above, we implement the following features:
1. In the onCreate() method, we invoke the initUI() method and create "SupportMapFragment" variable to call the OnMapReady() method asynchronously.
2. In the initUI() method, we create a GoogleMap variable, eight Buttons and three TextViews variables for the UI elements, then create the initUI() method to init the UI elements and implement their setOnClickListener method and pass "this" as the parameter.
3. Next, we override the onClick() method for the eight buttons.
4. Lastly, we override the onMapReady() method to initialize the mMap. Then add a marker of Palo Alto, California here for example. So when the Google map is loaded, you will see a red pin tag on Palo Alto, California.
We have gone through a long process to setup the UI of the application. Now, let's build and run the project and install it in your Android device to test it. Here we use Nexus 5 for testing. When the application is launched, press the Open button in the ConnectionActivity to open the MainActivity view, then you should see the following screenshot:
Registering Your Application
1. Modifying AndroidManifest file
After you finish the above steps, let's register our application with the App Key you apply from DJI Developer Website. If you are not familiar with the App Key, please check the Get Started.
Let's open the AndroidManifest.xml file and add the following elements on top of <application> element:
In the code above, we specify the permissions of your application needs by adding <uses-permission> elements as children of the <manifest> element.
Moreover, because not all Android-powered devices are guaranteed to support the USB accessory and host APIs, include two elements that declare that your application uses the "android.hardware.usb.accessory" and "android.hardware.usb.host" feature.
Finally, we need to specify the requirement for OpenGL ES version 2.
Please enter the App Key of the application in the value part of android:name="com.dji.sdk.API_KEY" attribute. For more details of the AndroidManifest.xml file, please check this tutorial's Github source code of the demo project.
2. Working on the GEODemoApplication and ConnectionAcitity
For the implementation of the registration logics in the "GEODemoApplication.java" and "ConnectionAcitity.java" files, we don't explain the details here. Please check this tutorial's Github source code.
Now let's build and run the project and install it to your Android device. If everything goes well, you should see the "Register Success" textView like the following screenshot when you register the app successfully.
Implementing GEO Features in MainActivity
Update the Connection Status TextView
Let's open MainActivity.java file and add the following two methods to update the mConnectStatusTextView content when product's connection changes:
In the code above, we invoke the getUserAccountState() method of UserAccountManager to fetch the current user account status and update the textView loginStatusTv's text content.
Next, let's implement the onClick() method for btnLogin and btnLogout buttons as shown below:
In the code above, we invoke the logIntoDJIUserAccount() method of UserAccountManager to present a login view for the user to login. When login success, we update the loginStatusTv's text content with the user account status. Similarly, invoke the logoutOfDJIUserAccount() method of UserAccountManager to log out the user account.
Working on GEO System Features
Update Fly Zone Info and Aircraft Location
If you want to unlock a fly zone, you may need to get the fly zone's ID first. Now let's update the fly zone info in the flyZonesTv and update the aircraft's location when simulated coordinate data changes.
Create the following variables above the onCreate() method as shown below:
In the code above, we mainly initialize the mFlightController variable and implement the setStateCallback() method of FlightController to invoke the updateDroneLocation() method to update the aircraft location on the map view when it's moving.
Moreover, let's update the onMapReady() method with the following codes:
@Override publicvoidonMapReady(GoogleMap googleMap){ LatLng paloAlto = new LatLng(37.4613697, -122.1237315);
In the code above, we initialize the mMap variable and invoke the moveCamera() and animateCamera() methods of GoogleMap class to move the camera and zoom in on the map. Then, invoke the printSurroundFlyZones() method to update the fly zone info.
Lastly, add the following two methods below the onMapReady() method as shown below:
In the code above, we invoke the getFlyZonesInSurroundingArea method of FlyZoneManager to fetch the fly zone informations list, then in the onSuccess() method, invoke the showSurroundFlyZonesInTv() method to update the fly zone infos on flyZonesTv textView.
Lastly, let's implement the OnClick() method of btnGetSurroundNFZ and btnUpdateLocation buttons as shown below:
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;
When you press the btnGetSurroundNFZ button, it will invoke the printSurroundFlyZones() method to update the fly zone infos on the flyZonesTv textView. When you press the btnUpdateLocation method, it will get the updated latitude and longitude data of the aircraft from DataOsdGetPushCommon and update the aircraft location on the map view. Then invoke the moveCamera() and animateCamera() method of GoogleMap to move and zoom in the camera to the aircraft's updated location on the map.
Unlock Fly Zones
Once you finished the above steps, let's create more variables on top of onCreate() method as shown below:
private MarkerOptions markerOptions = new MarkerOptions(); private ArrayList<Integer> unlockFlyZoneIds = new ArrayList<Integer>();
Next, update the onResume() method and add the following methods under the onCreate() method:
In the code above, we mainly override methods of android.app.Activity. In the onResume() method, we invoke the initFlightController() methods to initialize the mFlightController variable.
Next, add the following code at the bottom of onCreate() method:
The code above will show the fly forbid status to the user. For example, if the aircraft is approaching a restricted fly area, it will pop up a message to warn the user.
Before we unlock the fly zones, we should show the fly zone on the map by drawing polygons or circles with different colors. Let's define a unlockableIds arrayList, two int values and a FlyfrbBasePainter object above the onCreate() method first:
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();
For more details of the FlyfrbBasePainter class, please check the "FlyfrbBasePainter.java" file in this tutorial's Github Sample Project.
Then create the updateFlyZonesOnTheMap() method and invoke it in printSurroundFlyZones() method as shown below:
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) {
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; }
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()); } }); }
In the code above, we implement the following features:
In the updateFlyZonesOnTheMap() method, we use a for loop to get each flyZone object in the flyZones arraylist and then check if the flyZone has polygon fly zone and add different shape of fly zones with different colors on the map.
Next, in the printSurroundFlyZones() method, we invoke getFlyZonesInSurroundingArea method of FlyZoneManager to get the fly zone infos in the surrounding area. Then invoke the updateFlyZonesOnTheMap() method and pass the flyZones object to draw flyzone shapes on the map. Also, invoke the showSurroundFlyZonesInTv() method to show fly zone infos on the flyZonesTv textView.
Finally, let's implement the onClick() method of btnUnlock and btnGetUnlock buttons as shown below:
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) { case0: if (TextUtils.isEmpty(input.getText())) { dialog.dismiss(); } else { String value1 = input.getText().toString(); unlockFlyZoneIds.add(Integer.parseInt(value1)); } break; case1: 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) {
@Override public void onFailure(DJIError djiError) { showToast(djiError.getDescription()); } });
break;
In the code above, we implement the following features:
1. For the case of btnUnlock button, we create an "AlertDialog" with the title of "Unlock Fly Zone ID", and add a EditText with the hint of "Enter Fly Zone ID". Then create three items for Continue, Unlock, and Cancel actions:
Continue Action
It will add the current input fly zone ID to the unlockFlyZoneIds ArrayList and dismiss the "AlertDialog".
Unlock Action
It will add the current input fly zone ID to unlockFlyZoneIds ArrayList and invoke the unlockFlyZones() method of FlyZoneManager by passing the unlockFlyZoneIds array to unlock fly zones.
Cancel Action
It will dismiss the "AlertDialog".
2. For the case of btnGetUnlock button, we invoke the getUnlockedFlyZones() method of FlyZoneManager to fetch unlocked fly zone infos, then override the onSuccess() method and invoke the showSurroundFlyZonesInTv() method by passing the flyZoneInformations ArrayList to update the fly zone infos in the right flyZonesTv textView.
For more details of the implementation, please check this tutorial's Github Sample Project.
Running the Sample Code
We have gone through a long way so far, now, let's build and run the project, connect the demo application to your Phantom 4 (Please check the Run Application for more details) and check all the features we have implemented so far.
Unlock Authorization Fly Zone Workflow
1. Login your verified DJI account, if it's a new account, you need to complete the verification process.
2. Open the Simulator of DJI Assistant 2 or DJI PC Simulator and enter the coordinate data (37.4613697, -122.1237315) (Near Palo Alto Airport) to start simulating the aircraft's coordinate to the authorization area.
3. Press UPDATE LOCATION and GET SURROUNDING NFZ buttons to update the aircraft location on the map and update the fly zone information around the aircraft on the right textView.
4. Get the authorization fly zone ID you want to unlock from the textView, the category of it should be Authorization.
5. Press UNLOCK NFZS button and enter the fly zone ID to unlock it.
6. If you unlock the fly zone successfully, you can press the GET SURROUNDING NFZ button to refresh the fly zone infos on the right textView, you may notice that one of the yellow circle will disappear in the map. And you can take off the aircraft in the simulator now.
Note: Limited Simulation Area
Currently, you can only test the GEO feature within 50km of (37.453671, -122.118101), which is the location of Palo Alto Airport in California, United States.
Login and Logout DJI Account
1. Login DJI Account
Press the LOGIN button and a login view will pop up as shown below:
If it's a new DJI account, it will show a verification view as shown below:
2. Logout DJI Account
Press the LOGOUT button to logout your DJI account.
On the upper right corner of the screenshot, you can check the loginStatusTv's info for the user account status as shown below:
Use DJISimulator to Simulate Aircraft Location
We will use the DJISimulator to simulate the test environment to locate the aircraft to specific latitude and longitude coordinate.
If you are using Phantom 4 for testing, please check DJI Assistant 2 Simulator tutorial for details, otherwise, if you are using Phantom 3 Professional, Inspire 1, etc, please check DJI PC Simulator tutorial for details.
Open the Simulator of DJI Assistant 2 or DJI PC Simulator and enter the coordinate data (37.4613697, -122.1237315) (Near Palo Alto Airport) to start simulating the aircraft's coordinate to the authorization area.
Press UPDATE LOCATION and GET SURROUNDING NFZ buttons to update the aircraft's location on the map and update the fly zone information around the aircraft on the right textView.
Wait for a while, you may see there a red aircraft placed inside the yellow circle, which is an authorization fly zone you can unlock as shown below:
Also the textView on the right side will show the FlyZoneInformation info, includes the fly zone name, fly zone id (required in the unlock process), fly zone category, type, etc.
Here are the explanation of the three fly zone circles:
Green Circle
It represents the warning fly zones, which do not restrict flight and are informational to alert the user. In a warning fly zone, users should be prompted with a warning message describing the zone.
Yellow Circle
It represents the authorization fly zone, which will restrict the flight by default, it can be unlocked by a GEO verified user.
Red Circle
It represents the restricted fly zone, it will restrict the flight by default and cannot be unlocked by a GEO verified user.
Unlock and Get Unlock Fly Zones
1. Unlock Fly Zone
After you login with your DJI account and locate the aircraft to the coordinate of (37.4613697, -122.1237315), you can press the UNLOCK NFZS button and type in the fly zone ID to unlock it.
If you unlock the fly zone successfully, you can press the GET SURROUNDING NFZ button to refresh the fly zone infos on the right textView, and one of the yellow circle will disappear in the map as shown in the following gif animation:
2. Get Unlock Fly Zone List
You can press the GET SURROUNDING NFZ button to get the fly zone you have unlocked before on the right textView as shown below:
Lastly, please restart the aircraft to make the settings become effective.
Summary
In this tutorial, you've learned how to use the FlyZoneManager and FlyZoneInformation of DJI Mobile SDK to get the fly zone information, how to unlock authorization fly zones and how to add aircraft annotation and draw fly zone circle overlays on the map view to represent the fly zones. Moreover, you've learned how to use the DJISimulator to simulate the aircraft's coordinate and test the GEO System feature indoor without flying outside.
Hope this tutorial can help you integrate the GEO System feature in your DJI SDK based Application. Good luck, and hope you enjoyed this tutorial!