Skip to main content

Get started with positioning

|

The Positioning module enables your application to obtain and utilize location data, serving as the foundation for features like navigation, tracking, and location-based services. This data can be sourced either from the device's GPS or from a custom data source, offering flexibility to suit diverse application needs.

Using the Positioning module, you can:

  • Leverage real GPS data: Obtain highly accurate, real-time location updates directly from the device's built-in GPS sensor.
  • Integrate custom location data: Configure the module to use location data provided by external sources, such as mock services or specialized hardware.

In the following sections, you will learn how to grant the necessary location permissions for your app, set up live data sources, and manage location updates effectively. Additionally, we will explore how to customize the position tracker to align with your application's design and functionality requirements. This comprehensive guide will help you integrate robust and flexible positioning capabilities into your app.

Granting permissions

Add application location permissions

Location permissions must be configured in your Android application. In order to give the application the location permission on Android, you need to alter the app/src/main/AndroidManifest.xml and add the following permissions within the <manifest> block:

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

More information about permissions can be found in the Android Manifest documentation.

Defining location permissions in the application, as explained in the previous section, is a mandatory step.

// Check if location permissions are granted
if (ContextCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED
) {
// After the permission was granted, we can set the live data source (in most cases the GPS).
// The data source should be set only once, otherwise we'll get an error.
SdkCall.execute {
val liveDataSource = DataSourceFactory.produceLive()
liveDataSource?.let {
PositionService.dataSource = it
Log.d(TAG, "Live data source set successfully")
} ?: run {
Log.e(TAG, "Failed to create live data source")
}
}
} else {
// Request location permission
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
),
LOCATION_PERMISSION_REQUEST_CODE
)
}

Handle the permission request result in your activity:

override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)

when (requestCode) {
LOCATION_PERMISSION_REQUEST_CODE -> {
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted, set up live data source
SdkCall.execute {
val liveDataSource = DataSourceFactory.produceLive()
liveDataSource?.let {
PositionService.dataSource = it
Log.d(TAG, "Live data source set successfully")
}
}
} else {
// Permission denied
Log.w(TAG, "Location permission denied")
}
}
}
}

In the previous code, we request the location permissions. If the user grants them, we notify the engine to use real GPS positions for navigation by creating a live data source and setting it on the PositionService.

Alternatively, you can use PermissionHelper from the SDK utility package to simplify permission handling:

SdkCall.execute {
val hasLocationPermission =
PermissionsHelper.hasPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
if (hasLocationPermission)
SdkCall.execute {
val liveDataSource = DataSourceFactory.produceLive()
liveDataSource?.let {
PositionService.dataSource = it
Log.d(TAG, "Live data source set successfully")
}
}
else
requestPermissions(this)
}

If the live data source is set and the right permissions are granted, the position cursor should be visible on the map as an arrow.

Tip

For debugging purposes, the Android Emulator's extended controls can be used to mock the current position. On a real device, apps such as Mock Locations can be utilized to simulate location data.

Don't forget to add the necessary import statements and member declarations to your activity:

import android.Manifest
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.magiclane.sdk.sensordatasource.DataSourceFactory
import com.magiclane.sdk.sensordatasource.PositionService
import com.magiclane.sdk.util.SdkCall

class YourActivity : AppCompatActivity() {
// ...
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1001
private const val TAG = "PositioningExample"
}
}

Receive location updates

To receive updates about changes in the current position, we can register a callback function using the PositionListener. The given listener will be called continuously as new updates about current position are made.

Tip

Consult the Positions guide for more information about the PositionData class and the differences between raw positions and map matched position.

Raw positions

To listen for raw position updates (as they are pushed to the data source or received via the sensors), you can add a PositionListener for the EDataType.Position:

val positionListener = PositionListener { position: PositionData ->
// Process the position
if (position.isValid()) {
val coordinates = position.coordinates
println("New raw position: $coordinates")
}
}

// Add the listener
PositionService.addListener(positionListener, EDataType.Position)

Map matched positions

To register a callback to receive map matched positions, use EDataType.ImprovedPosition:

val improvedPositionListener = PositionListener { position: PositionData ->
if (position.isValid() && position is ImprovedPositionData) {
// Current coordinates
val coordinates = position.coordinates
println("New improved position: $coordinates")

// Speed in m/s (-1 if not available)
val speed = position.speed
println("Speed: $speed m/s")

// Speed limit in m/s on the current road (0 if not available)
val speedLimit = position.roadSpeedLimit
println("Speed limit: $speedLimit m/s")

// Heading angle in degrees (N=0, E=90, S=180, W=270, -1 if not available)
val course = position.course
println("Course: $course degrees")

// Information about current road (if it is in a tunnel, bridge, ramp, one way, etc.)
val roadModifiers = position.roadModifier
println("Road modifiers: $roadModifiers")

// Quality of the current position
val fixQuality = position.fixQuality
println("Fix quality: $fixQuality")

// Horizontal and vertical accuracy in meters
val horizontalAccuracy = position.horizontalAccuracy
val verticalAccuracy = position.verticalAccuracy
println("Accuracy - H: $horizontalAccuracy, V: $verticalAccuracy")

// Road address information (if available)
if (position.hasRoadLocalization()) {
val roadAddress = position.roadAddress
println("Road address: $roadAddress")
}
}
}

// Add the listener
PositionService.addListener(improvedPositionListener, EDataType.ImprovedPosition)

Remember to remove listeners when they are no longer needed:

// Remove specific listener
PositionService.removeListener(positionListener)
PositionService.removeListener(improvedPositionListener)
info

During simulation, the positions provided through the PositionListener methods correspond to the simulated locations generated as part of the navigation simulation process.

Get current location

To retrieve the current location, we can use the position and improvedPosition properties from the PositionService object. These properties return a PositionData object containing the latest location information or null if no position data is available. This method is useful for accessing the most recent position data without registering for continuous updates.

SdkCall.execute {
val position = PositionService.position

if (position == null) {
Log.w(TAG, "No position available")
} else {
Log.d(TAG, "Position: ${position.coordinates}")
}
}

For map-matched positions, use the improvedPosition property:

SdkCall.execute {
val improvedPosition = PositionService.improvedPosition

if (improvedPosition == null) {
Log.w(TAG, "No improved position available")
} else {
Log.d(TAG, "Improved position: ${improvedPosition.coordinates}")

// Access additional improved position data
if (improvedPosition is ImprovedPositionData) {
if (improvedPosition.hasRoadLocalization()) {
val roadAddress = improvedPosition.roadAddress
println("Current road: $roadAddress")
}

val speedLimit = improvedPosition.roadSpeedLimit
println("Speed limit: $speedLimit m/s")
}
}
}

You can also use the convenience methods to get coordinates directly:

SdkCall.execute {
val currentCoords = PositionService.getCurrentPosition()
val improvedCoords = PositionService.getCurrentImprovedPosition()

currentCoords?.let {
println("Current coordinates: $it")
}

improvedCoords?.let {
println("Improved coordinates: $it")
}
}