Skip to 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.

  • Kotlin
  • Java
// Kotlin
// 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 {
            UnlPositionService.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
    )
}

// Java
// 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(() -> {
        DataSource liveDataSource = DataSourceFactory.produceLive();
        if (liveDataSource != null) {
            UnlPositionService.setDataSource(liveDataSource);
            Log.d(TAG, "Live data source set successfully");
        } else {
            Log.e(TAG, "Failed to create live data source");
        }
    });
} else {
    // Request location permission
    ActivityCompat.requestPermissions(
        this,
        new String[]{
            Manifest.permission.ACCESS_FINE_LOCATION,
            Manifest.permission.ACCESS_COARSE_LOCATION
        },
        LOCATION_PERMISSION_REQUEST_CODE
    );
}

Handle the permission request result in your activity:

  • Kotlin
  • Java
// Kotlin
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 {
                        UnlPositionService.dataSource = it
                        Log.d(TAG, "Live data source set successfully")
                    }
                }
            } else {
                // Permission denied
                Log.w(TAG, "Location permission denied")
            }
        }
    }
}

// Java
@Override
public void onRequestPermissionsResult(
    int requestCode,
    String[] permissions,
    int[] grantResults
) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);

    switch (requestCode) {
        case LOCATION_PERMISSION_REQUEST_CODE:
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission granted, set up live data source
                SdkCall.execute(() -> {
                    DataSource liveDataSource = DataSourceFactory.produceLive();
                    if (liveDataSource != null) {
                        UnlPositionService.setDataSource(liveDataSource);
                        Log.d(TAG, "Live data source set successfully");
                    }
                });
            } else {
                // Permission denied
                Log.w(TAG, "Location permission denied");
            }
            break;
    }
}

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 UnlPositionService.

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

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

// Java
SdkCall.execute(() -> {
    boolean hasLocationPermission =
        PermissionsHelper.hasPermission(this, Manifest.permission.ACCESS_FINE_LOCATION);
    if (hasLocationPermission) {
        SdkCall.execute(() -> {
            DataSource liveDataSource = DataSourceFactory.produceLive();
            if (liveDataSource != null) {
                UnlPositionService.setDataSource(liveDataSource);
                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:

  • Kotlin
  • Java
// Kotlin
import android.Manifest
import android.content.pm.PackageManager
import android.util.Log
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.unlmap.sdk.sensordatasource.DataSourceFactory
import com.unlmap.sdk.sensordatasource.UnlPositionService
import com.unlmap.sdk.util.SdkCall

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

// Java
import android.Manifest;
import android.content.pm.PackageManager;
import android.util.Log;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import com.unlmap.sdk.sensordatasource.DataSourceFactory;
import com.unlmap.sdk.sensordatasource.UnlPositionService;
import com.unlmap.sdk.util.SdkCall;

public class YourActivity extends AppCompatActivity {
    // ...
    private static final int LOCATION_PERMISSION_REQUEST_CODE = 1001;
    private static final String TAG = "PositioningExample";
}

Receive location updates

To receive updates about changes in the current position, we can register a callback function using the UnlPositionListener. 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 UnlPositionData 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 UnlPositionListener for the EDataType.Position:

  • Kotlin
  • Java
// Kotlin
val positionListener = UnlPositionListener { position: UnlPositionData ->
    // Process the position
    if (position.isValid()) {
        val coordinates = position.coordinates
        println("New raw position: $coordinates")
    }
}

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

// Java
UnlPositionListener positionListener = (UnlPositionData position) -> {
    // Process the position
    if (position.isValid()) {
        UnlCoordinates coordinates = position.getCoordinates();
        System.out.println("New raw position: " + coordinates);
    }
};

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

Map matched positions

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

  • Kotlin
  • Java
// Kotlin
val improvedPositionListener = UnlPositionListener { position: UnlPositionData ->
    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
UnlPositionService.addListener(improvedPositionListener, EDataType.ImprovedPosition)

// Java
UnlPositionListener improvedPositionListener = (UnlPositionData position) -> {
    if (position.isValid() && position instanceof ImprovedPositionData) {
        ImprovedPositionData improvedPosition = (ImprovedPositionData) position;
        // Current coordinates
        UnlCoordinates coordinates = improvedPosition.getCoordinates();
        System.out.println("New improved position: " + coordinates);

        // Speed in m/s (-1 if not available)
        double speed = improvedPosition.getSpeed();
        System.out.println("Speed: " + speed + " m/s");

        // Speed limit in m/s on the current road (0 if not available)
        double speedLimit = improvedPosition.getRoadSpeedLimit();
        System.out.println("Speed limit: " + speedLimit + " m/s");

        // Heading angle in degrees (N=0, E=90, S=180, W=270, -1 if not available)
        double course = improvedPosition.getCourse();
        System.out.println("Course: " + course + " degrees");

        // Information about current road (if it is in a tunnel, bridge, ramp, one way, etc.)
        int roadModifiers = improvedPosition.getRoadModifier();
        System.out.println("Road modifiers: " + roadModifiers);

        // Quality of the current position
        int fixQuality = improvedPosition.getFixQuality();
        System.out.println("Fix quality: " + fixQuality);

        // Horizontal and vertical accuracy in meters
        double horizontalAccuracy = improvedPosition.getHorizontalAccuracy();
        double verticalAccuracy = improvedPosition.getVerticalAccuracy();
        System.out.println("Accuracy - H: " + horizontalAccuracy + ", V: " + verticalAccuracy);

        // Road address information (if available)
        if (improvedPosition.hasRoadLocalization()) {
            String roadAddress = improvedPosition.getRoadAddress();
            System.out.println("Road address: " + roadAddress);
        }
    }
};

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

Remember to remove listeners when they are no longer needed:

  • Kotlin
  • Java
// Kotlin
// Remove specific listener
UnlPositionService.removeListener(positionListener)
UnlPositionService.removeListener(improvedPositionListener)

// Java
// Remove specific listener
UnlPositionService.removeListener(positionListener);
UnlPositionService.removeListener(improvedPositionListener);

📝 Info

During simulation, the positions provided through the UnlPositionListener 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 UnlPositionService object. These properties return a UnlPositionData 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.

  • Kotlin
  • Java
// Kotlin
SdkCall.execute {
    val position = UnlPositionService.position

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

// Java
SdkCall.execute(() -> {
    UnlPositionData position = UnlPositionService.getPosition();

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

For map-matched positions, use the improvedPosition property:

  • Kotlin
  • Java
// Kotlin
SdkCall.execute {
    val improvedPosition = UnlPositionService.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")
        }
    }
}

// Java
SdkCall.execute(() -> {
    UnlPositionData improvedPosition = UnlPositionService.getImprovedPosition();

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

        // Access additional improved position data
        if (improvedPosition instanceof ImprovedPositionData) {
            ImprovedPositionData improved = (ImprovedPositionData) improvedPosition;
            if (improved.hasRoadLocalization()) {
                String roadAddress = improved.getRoadAddress();
                System.out.println("Current road: " + roadAddress);
            }

            double speedLimit = improved.getRoadSpeedLimit();
            System.out.println("Speed limit: " + speedLimit + " m/s");
        }
    }
});

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

  • Kotlin
  • Java
// Kotlin
SdkCall.execute {
    val currentCoords = UnlPositionService.getCurrentPosition()
    val improvedCoords = UnlPositionService.getCurrentImprovedPosition()

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

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

// Java
SdkCall.execute(() -> {
    UnlCoordinates currentCoords = UnlPositionService.getCurrentPosition();
    UnlCoordinates improvedCoords = UnlPositionService.getCurrentImprovedPosition();

    if (currentCoords != null) {
        System.out.println("Current coordinates: " + currentCoords);
    }

    if (improvedCoords != null) {
        System.out.println("Improved coordinates: " + improvedCoords);
    }
});