03 Current Position
Overview
You will learn how to use the android Location Service to acquire the current car position and strategies to display it on the map.
Base
Use Tutorial02
as a starting point for this project.
Requesting Location Updates
For this purpose, we will use the Android LocationManager to acquire location updates. For more advanced strategies to acquire location updates see Location Strategies.
To retrieve location updates, you must first request permission to access the NETWORK_PROVIDER
or GPS_PROVIDER
in your AndroidManifest.xml
.
<manifest ... > <!-- permission to access the GPS_PROVIDER --> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <!-- permission to access the NETWORK_PROVIDER --> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> ... </manifest>
Attention
Be sure to set the Target-SDK version of your project to 22
to avoid problems with permission requests. Android target versions > 22 will not grant permissions to any resources like GPS or network when the user not explicitly allows this. If you want to use a newer target version, please implement the user request for yourself.
For retrieving location updates, you have to implement the LocationListener interface and request updates for either the NETWORK_PROVIDER
or the GPS_PROVIDER
. For this example we implement the LocationListener interface as a field locationListener
in our MainActivity
and request the updates in the onCreate
method. We call removeUpdates
in the onDestroy method to stop the location updates.
public class MainActivity extends AppCompatActivity { ... private LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { // here we will retreive new locations } @Override public void onStatusChanged(String s, int i, Bundle bundle) {} @Override public void onProviderEnabled(String s) {} @Override public void onProviderDisabled(String s) {} }; private LocationManager locationManager; ... @Override protected void onCreate(Bundle savedInstanceState) { ... locationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE); // request update use NETWORK provider if its sufficent to retreive less accurate positions locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener); } @Override protected void onDestroy() { super.onDestroy(); locationManager.removeUpdates(locationListener); } ... }
Notifying the SDK
This would be enough to get the current position and show it on the MapView. But we also need to notify the SDK about the location updates. So we can use the position in a navigation for example. For this reason, we provide the GPSManager singleton in the NavigationSDK. We need to initialize it by calling openGPSDevice
in our Application class.
public class Application extends android.app.Application { ... @Override public void onCreate() { ... try { GPSManager.getInstance().openGPSDevice(false); } catch (NavigationException e) { Log.e("Tutorial", "Unable to open GPS device: " + e.getMessage()); startupException = e; return; } }
And then we need to push the location we are getting in onLocationChanged
to the SDK.
public class MainActivity extends AppCompatActivity { ... private LocationListener locationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { GPSManager.getInstance().pushLocation(location); } ... }; ... }
The NavigationLoop
To update our MapView with the current position, we implement one of the core mechanisms of this Tutorial. The NavigationLoop is a runnable which is called every second by a timer to update our navigation state. We extend java.lang.Runnable. To implement this, we create a TimerTask in our MainActivity, start it in onCreate
and stop it in onDestroy
. Also we create a NavigationLoop member in our MainActivity, instantiate it in onCreate
and call the run()
method in the TimerTask.
public class NavigationLoop implements Runnable { public NavigationLoop() { } @Override public void run() { // here we will update our navigation state } }
public class MainActivity extends AppCompatActivity { NavigationLoop navigationLoop; ... TimerTask navigationLoopTrigger = new TimerTask(){ @Override public void run() { navigationLoop.run(); } }; @Override protected void onCreate(Bundle savedInstanceState) { ... navigationLoop = new NavigationLoop(mapView); Timer t = new Timer(); t.schedule(navigationLoopTrigger, 0, 1000); } @Override protected void onDestroy() { ... navigationLoopTrigger.cancel(); ... } }
To avoid updating our MainActivity or our MapView directly we also introduce a new interface called NavigationControl
. Currently it will constist only of one callback function named onGPS which the NavigationLoop will call every run.
interface NavigationControl { void onGPS(final GPSData gps); }
Because there will be more controls than only the MapView which possibly want to listen to our onGPS callback we store a List of them in our NavigationLoop:
class NavigationLoop implements Runnable { private final List<NavigationControl> controls = new ArrayList<>(); public NavigationLoop() { } public void addControl(NavigationControl control) { controls.add(control); } public void removeControl(NavigationControl control) { controls.remove(control); } ... }
We get the current GPS position from our GPSManager and notify all NavigationControls. To avoid notifying controls with a bogus position, we check if GPSData has a Fix with getFix()
before calling onGPS.
class NavigationLoop implements Runnable { ... @Override public void run() { try { final GPSData gps = GPSManager.getInstance().getCurrentPosition(); if (gps.getFix() > 0) { for (NavigationControl control : controls) { control.onGPS(gps); } } } catch (NavigationException ignored) { } } }
Update the map position
To update our MapView position we implement the NavigationControl Interface in our MapView class and update the center with the given GPSData
public class MapView extends com.ptvag.navigation.sdk.MapView implements NavigationControl { ... @Override public void onGPS(GPSData gps) { setCenter(gps.getGPSPositionMerc()); update(); // force a redraw of the map with the new center } }
Don't forget to register the MapView as a NavigationControl to the NavigationLoop.
public class MainActivity extends AppCompatActivity { private MapView mapView; ... @Override protected void onCreate(Bundle savedInstanceState) { ... setContentView(R.layout.activity_main); mapView = (MapView)findViewById(R.id.map); ... navigationLoop = new NavigationLoop(); navigationLoop.addControl(mapView); } ... }
Draw the CCP
The easiest way to draw the current car position (CCP) is to use MapView.setMapMarker()
. It comes in two styles:
MapMarkerStyles | |
---|---|
MapView.MapMarkerStyle.Round |
MapView.MapMarkerStyle.Directed |
We just set the MapMarker with the mercator position the orientation(course) of the GPSData and the desired style.
public class MapView extends com.ptvag.navigation.sdk.MapView implements NavigationControl { ... @Override public void onGPS(GPSData gps) { setMapMarker( gps.getGPSPositionMerc(), gps.getCourse(), true, com.ptvag.navigation.sdk.MapView.MapMarkerStyle.Directed ); setCenter(gps.getGPSPositionMerc()); update(); } }