Find your location

One of the main reasons to use a mapping application is to find out where you are.

The LocationEngine provided by the HERE SDK implements a comprehensive location solution that works with several location sources such as GPS or other Global Navigation Satellite System (GNSS) receivers, mobile network signals and Wi-Fi network signals to determine accurate locations.

If an iOS device itself is capable of GNSS precision depends on the hardware of the iOS device. Sub-meter accuracy is currently not supported by iOS and would require a separate receiver. For Android devices sub-meter accuracy is only supported when your credentials are enabled for this (see below).

Note

At a glance

Integrating the HERE SDK location features requires at least the following steps:

  1. Add the required permissions to your manifest file (for Android) and your .plist file (for iOS) and request the permissions from the user.
  2. Create a ConsentEngine and show a consent dialog whether data can be collected by the LocationEngine or not.
  3. Show the outcome of the consent dialog and allow the user to revoke a previous decision.
  4. Create a LocationEngine and set at least one LocationListener.
  5. Start the LocationEngine once and set the desired accuracy level.
  6. Receive Location updates and handle them in your app.

Add the required permissions

Flutter itself provides no permission handling. Therefore, you need to adapt a few generated files for your Android and iOS native projects.

Let's start with Android.

Before you can start using the LocationEngine in your app on Android devices, you will need to add the required permissions to the app's AndroidManifest.xml file (located in your_project_dir/android/app/src/main/):

...
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />

Note

If your application targets Android SDK version 31 or higher, users of your application need to grant the device's "precise" location. When being prompted, it is not enough to select the "approximate" precision. Therefore, ACCESS_COARSE_LOCATION and ACCESS_FINE_LOCATION permissions need to be present in your manifest file, as shown above. HERE positioning needs the fine location permission in order to use GNSS and to make cellular and WiFi scans. The ACCESS_COARSE_LOCATION permission alone is not enough as it would result in an approximate precision which is not enough for HERE positioning to work. In that case, the LocationEngine would fail with a MISSING_PERMISSIONS error.

The WAKE_LOCK permission is not enforced by the HERE SDK, however, if the permission is granted for the application, HERE SDK will utilise wake locks to ensure that network scans and position calculation are not interrupted by the device going in power save mode. Wake locks are kept only for minimum required time to keep impact on battery consumption as low as possible. It should be noted that Android operating system blames the battery consumption to the application or service that is keeping a wake lock for its duration, so to keep your application appealing for the users you should carefully consider whether wake locks are mandatory for your use case or not.

Next, we can move on to iOS.

Add to the app's Info.plist file (located in your_project_dir/ios/Runner/):

<key>UIRequiredDeviceCapabilities</key>
<array>
   <string>location-services</string>
   <string>gps</string>
</array>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
   <string>This app needs to access your current location to display it on the map.</string>
<key>NSLocationWhenInUseUsageDescription</key>
   <string>This app needs to access your current location to display it on the map.</string>
<key>NSMotionUsageDescription</key>
   <string>Motion detection is needed to determine more accurate locations, when no GPS signal is found or used.</string>

Note

Note that permission NSLocationAlwaysAndWhenInUseUsageDescription is needed only if your application wants to request location updates while on background.

The permission_handler plugin can be used to request specific permissions from the user. Please refer to the plugin's official site for more information. Alternatively, you can find platform specific code for requesting permissions in the "Find your Location" section of the Developer Guide for the HERE SDK for Android and Developer Guide for the HERE SDK for iOS.

An app using native location services such as GPS will ask for the user's permission. Not all devices provide the same capabilities and may have certain hardware restrictions that can lead to varying results.

Note

Even recent iPad or Android tablet devices can lack the required UIRequiredDeviceCapabilities for gps. If present in the Info.plist file, the app will not install on such devices: If you remove this capability, the app will install, but the Location updates you will receive from the device will have a larger accuracy radius and are therefore quite imprecise - if there is no GPS sensor, the device may fall back to, for example, network positioning.

Prior to using the LocationEngine, it may be a good idea to check if the native location services are enabled. On most Android devices a user can decide to turn the location services on, and even increase the accuracy, by opening the device's Settings and navigating to the Security & location section. On most iOS devices, a user can navigate to Settings > Privacy > Location Services to make sure that the location services are on.

The LocationEngine contains functionality that can gather certain information about the surroundings of the mobile device in order to improve the HERE services used by the HERE SDK. An example of such information is the strength of the nearby Wi-Fi and mobile network signals.

The HERE SDK provides a ConsentEngine that handles the flow to acquire the user’s consent to collect such data. In addition, it allows to retrieve the current status and to revoke a previous decision whether to collect data or not. The application must ensure that this is accessible for the user at all times.

Note: Requirement

Showing a consent dialog is mandatory. The LocationEngine will not deliver location data to the app until the user has made a decision. Note that the LocationEngine will be fully operable regardless if the consent is granted or declined by a user.

Two steps are required:

  • Show the consent dialog by calling consentEngine.requestUserConsent().
  • Your application must ensure to show the user's current decision and to revoke a previous decision: Get the current decision via consentEngine.userConsentState. Revoke a previous decision by allowing the user to call consentEngine.requestUserConsent() again. This must be possible at any time during the lifecycle of your app.

These steps are explained in greater detail below. See the HERE SDK Privacy Supplement for more information. Note that information is only collected if the user has given their consent. Gathered information does not identify the user, and HERE will only retain anonymized information.

Note: The above stated requirement only applies to Android applications, as currently there is no data collection in iOS platforms. This might change in the future.

On iOS devices, calling consentEngine.requestUserConsent() will show a dialog, but it is not needed to be called. The default consent state is notHandled. Although on iOS the ConsentEngine's methods and properties (userConsentState, grantUserConsent() and denyUserConsent()) work the same as on Android, it is recommended to not handle them on iOS. On Android, a notHandled state will not allow you to use the LocationEngine as you need to request consent to set a valid state (granted or denied). On iOS, the LocationEngine will work out-of-the-box, regardless of the state.

Setting localization

Consent engine supports upto 31 different languages. Language used when the dialog is shown is automatically selected based on current device language setting. In case the device language is not in supported languages, English will be used instead. The code snippet below shows how to configure your application to support localized consent dialog.

// File: main.dart

import 'package:flutter/material.dart';
import 'package:here_sdk/consent.dart';

import 'PositioningExample.dart';

// ...

void main() {
    runApp(
        MaterialApp(
            // Add consent localization delegates.
            localizationsDelegates: HereSdkConsentLocalizations.localizationsDelegates,
            // Add supported locales.
            supportedLocales: HereSdkConsentLocalizations.supportedLocales,
            home: MyApp(),
        ),
    );
}

// ...

class MyApp extends StatefulWidget {
    
    PositioningExample createState() => PositioningExample();
}

Before starting the LocationEngine, you need to ensure that the user's consent to collect the before mentioned information has been handled. It does not matter what the answer was (if the user accepted the collection of data or not), only that they were shown the consent dialog and that an answer was given. The LocationEngine will return LocationEngineStatus.USER_CONSENT_NOT_HANDLED status when attempting to start it without having handled the user's consent.

The code snippet below creates an instance of the ConsentEngine, checks if the user’s consent has already been requested and if not, invokes a UI dialog (pictured also below) which explains the details of information gathering to the user, and provides them with the possibility to either grant or deny permission.

try {
  _consentEngine = ConsentEngine();
} on InstantiationException {
  throw ("Initialization of ConsentEngine failed.");
}

// ...

Future<void> _ensureUserConsentRequested() async {
  // Check if user consent has been handled.
  if (_consentEngine.userConsentState == ConsentUserReply.notHandled) {
    // Show dialog.
    await _consentEngine.requestUserConsent(context);
  }

  _updateConsentInfo();
  _startLocating();
}

It is recommended to not call requestUserConsent() while loading a map scene.

Note that the code for _updateConsentInfo() is left out here. It basically shows the current consent decision of the user.

Screenshot: Consent dialog.

The dialog contains a link to a web page describing the privacy practices of HERE and supports 37 languages. When shown, the dialog will be displayed according to the device's language preferences, or in English, if they are not supported.

The user's response persists between the usage sessions of the application and can be retrieved with the userConsentState property, which returns a Consent.UserReply value:

switch (_consentEngine.userConsentState) {
  case ConsentUserReply.granted:
    //The user has previously given permission.
    break;
  case ConsentUserReply.denied:
    // The user has previously denied permission.
    break;
  case ConsentUserReply.notHandled:
    //The user has not been asked for consent.
    break;
  case ConsentUserReply.requesting:
    //The dialog is currently being shown to the user.
    break;
  default:
    throw Exception("Unknown consent state.");
}

Keep in mind that the application must provide the possibility for the user to see what response they have given earlier, by accessing the userConsentState property. In addition, the application must also make it possible for the user to change their mind on the consent at any time, by calling the requestUserConsent() method and displaying the consent dialog again.

On Android, when requestUserConsent() is called the HERE SDK shows a new Activity containing the dialog. When the dialog is dismissed, the previous Activity is resumed. If a MapView is shown before the dialog is opened, the MapView will be paused until it is shown again.

Note: Important

Currently there is no data collection on iOS platforms. This might change in the future. In the meantime, the above stated requirement only applies to Android applications. The code snippet below shows one possible way to implement a screen that shows, on Android platforms, what response the user has given to the consent as well as allows them to change their mind, and on other platforms shows a message informing the user that there is no data collection.

// ...

// Platform class from dart:io library provides a way to check on what platform your app is running.
import 'dart:io' show Platform;

// ...

class ConsentPreferencesPage extends StatefulWidget {
  ConsentPreferencesPage({Key key, this.title}) : super(key: key);

  final String title;

  
  State<ConsentPreferencesPage> createState() {
    //Use different states according to the platform the app is running on.
    if (Platform.isAndroid) {
      return _ConsentPreferencesPageAndroid();
    }
    return _ConsentPreferencesPageOther();
  }

// ...

// This state shows what response the user has given to the consent, by accessing the consentEngine.userConsentState property. Additionally, it allows the user to change their mind on the consent by including a button "Manage Consent" that calls the requestUserConsent() method and displays the consent dialog again.
class _ConsentPreferencesPageAndroid extends State<ConsentPreferencesPage> with WidgetsBindingObserver {
  try {
    _consentEngine = ConsentEngine();
  } on InstantiationException {
    throw ("Initialization of ConsentEngine failed.");
  }

  String _getConsentAnswer() {
    switch (_consentEngine.userConsentState) {
      case ConsentUserReply.granted:
        //The user has previously given permission.
        return "You have granted consent to the data collection.";
      case ConsentUserReply.denied:
        // The user has previously denied permission.
      case ConsentUserReply.notHandled:
        //The user has not been asked for consent.
      case ConsentUserReply.requesting:
        //The dialog is currently being shown to the user.
        return "You have denied consent to the data collection.";
      default:
        throw Exception("Unknown consent state.");
    }
  }

  // ...

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        margin: const EdgeInsets.only(left: 25, right: 25),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Flexible(
                child: Text(
                    'The Location Engine contains functionality that can gather certain '
                        'information about the surroundings of the mobile device in order '
                        'to improve the HERE services used by the HERE SDK. An example '
                        'of such information is the strength of the nearby Wi-Fi and '
                        'mobile network signals.'
                ),
            ),
            SizedBox(height: 20),
            Flexible(
                child: Text(
                    _getConsentAnswer(),
                ),
            ),
            SizedBox(height: 20),
            PlatformButton(
                child: const Text("Manage Consent"),
                onPressed: () {
                  setState(() {
                    _consentEngine.requestUserConsent(context);
                  });
                }
            ),
          ],
        ),
      ),
    );
  }

// ...

// This state informs the user that there is currently no data collection.
class _ConsentPreferencesPageOther extends State<ConsentPreferencesPage> {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Container(
        margin: const EdgeInsets.only(left: 25, right: 25),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Flexible(
              child: Text('This app does not collect any data on this platform.'),
            ),
          ]
        )
      )
    );
  }
}

Note

An example flow can be seen in the Positioning example app you can find on GitHub.

In order to use the LocationEngine, it is mandatory to request the consent from your users. By default, this happens with the help of the above APIs.

It is possible to customize the look and the content of the dialog. For this, ask HERE to certify the application’s own customized user consent dialog. Once the certification is received the application can use the methods grantUserConsent() and denyUserConsent() to communicate the user’s response to the HERE SDK - calling requestUserConsent() is no longer necessary when HERE approves that a custom dialog can be initiated by an application. The communicated user’s response is persisted by the HERE SDK, and the method getUserConsentState() can be used to retrieve the previously given response.

It is, under certain pre-requirements, also possible to request or update the consent decision in a different way: For example, if your users have given their consent already on an external website. However, this has to be discussed with the HERE team individually for your solution.

Learn more about this option or initiate the certification process by contacting HERE via your HERE Sales representative or via our help page.

Create a LocationEngine

Creating a new LocationEngine is simple:

try {
  _locationEngine = LocationEngine();
} on InstantiationException {
  throw ("Initialization of LocationEngine failed.");
}

Note

It is not possible to initialize the LocationEngine during the Application's onCreate() lifecycle. Any other point in time is fine. For example, a good place to initialize the engine may be in an Activity's onCreate()-method.

Get the last known location

Once the engine is initialized, the last known location can be obtained, as long as the engine has been started at least once before and received at least one position, otherwise null will be returned. This information will remain, so the last known location will also be available between application sessions.

Location? myLastLocation = _locationEngine.lastKnownLocation;

if (myLastLocation != null) {
    // Log the last known location coordinates.
    print("Last known location: " + myLastLocation.coordinates.latitude.toString() + ", " + myLastLocation.coordinates.longitude.toString());
}

Note

The LocationEngine does not need to be started nor any listener needs to be set in order to get the last known location. It is enough that the LocationEngine was successfully started once in a previous session and that a valid location event was received at least once. The Location object contains a timestamp that indicates when that location was received.

Get notified on location events

Next before starting the LocationEngine, it's a good idea to register a LocationStatusListener so that you will be notified of changes in the engine's status. To do so, implement the LocationStatusListener abstract class and register it with the location engine's addLocationStatusListener() method. Check the API Reference for more information on the different statuses.

class PositioningExample extends State<MyApp> implements LocationStatusListener {

  
  onFeaturesNotAvailable(List<LocationFeature> features) {
    for (var feature in features) {
      print("Feature not available: " + feature.toString());
    }
  }

  
  onStatusChanged(LocationEngineStatus locationEngineStatus) {
    print("LocationEngineStatus: " + locationEngineStatus.toString());
  }

// ...

// Add the listener.
_locationEngine.addLocationStatusListener(this);

Note

After a successful start, LocationStatusListener will always receive status LocationEngineStatus.engineStarted, and after a successful stop, it will always receive status LocationEngineStatus.engineStopped.

Additionally, through the listener's onFeaturesNotAvailable() callback you will be notified of any LocationFeature that is not available. If a feature that you need is not available, contact your HERE representative.

The last thing to consider before starting the engine is registering a LocationListener, which provides the onLocationUpdated() callback that sends a notification once a new Location is detected. You can do so in a similar way as with the previously mentioned LocationStatusListener:

class PositioningExample extends State<MyApp> implements LocationListener {
    
    onLocationUpdated(Location location) {
        print("Received location: " + location.coordinates.latitude.toString() + ", " + location.coordinates.longitude.toString());
    }

// ...

// Add the listener.
_locationEngine.addLocationListener(this);

Note

The callback onLocationUpdated() is received on the main thread - same as for all other callbacks.

Except for the current geographic coordinates and the timestamp, all other Location fields are optional. For example, the received Location object may contain the information about the bearing angle, as well as the current speed, but this is not guaranteed to be available. Unavailable values will be returned as null. What kind of sources are used for positioning (as defined by the LocationAccuracy used to start the engine, see the Start and Stop Receiving Locations section below), and the device's capabilities affect what fields will be available.

You can add as many LocationStatusListener and LocationListener as you need by calling the respective addLocationStatusListener() and addLocationListener() methods.

Start and stop receiving locations

You are now ready to call the LocationEngine's startWithLocationAccuracy() method:

class PositioningExample extends State<MyApp> implements LocationListener, LocationStatusListener {
    try {
      _locationEngine = LocationEngine();
    } on InstantiationException {
      throw ("Initialization of LocationEngine failed.");
    }

    try {
      _consentEngine = ConsentEngine();
    } on InstantiationException {
      throw ("Initialization of ConsentEngine failed.");
    }

// ...

    if (_consentEngine.userConsentState == ConsentUserReply.notHandled) {
        await _consentEngine.requestUserConsent(context);
    }

    _updateConsentInfo();
    _startLocating();

// ...

void startLocating() {
    _locationEngine.addLocationListener(this);
    _locationEngine.addLocationStatusListener(this);
    _locationEngine.startWithLocationAccuracy(LocationAccuracy.bestAvailable);
}

The most straightforward way to start the engine is by passing it one of the pre-defined LocationAccuracy modes, as in the code snippet above. See the table below or check the API Reference for more information about all the available modes.

After the LocationEngine has been started, you will receive LocationEngineStatus.alreadyStarted if you try to start it again without calling stop() first. You can use the method isStarted() to check if the engine is started or not. Similarly, if you have started a LocationEngine and try to start another one without stopping the first, you will get LocationEngineStatus.alreadyStarted error. Only one engine can be started at a time.

If you don't want to receive more location updates, you can stop the engine by calling the stop() method. Remember to remove the listeners when they are no longer needed:

void stopLocating() {
    _locationEngine.removeLocationStatusListener(this);
    _locationEngine.removeLocationListener(this);
    _locationEngine.stop();
}

In general, it is recommended to listen to the AppLifecycleState and to stop the LocationEngine when an app gets disposed, see the positioning_app for an example on GitHub.

Pause updates while stationary on iOS devices

On iOS devices the LocationEngine will, by default, pause the location updates when location data is not expected to change. This can be used, for example, to improve battery life when the device is stationary. This feature can be controlled by the method LocationEngine.setPauseLocationUpdatesAutomatically().

Note

On Android plaftorms, setPauseLocationUpdatesAutomatically() will return LocationEngineStatus.notSupported.

Enable background updates

In case you want to continue receiving location updates while the application is running in the background, you first need to enable such capability by adding the needed permissions. On Android, this is only necessary if you target Android 10 (API level 29) or higher. Add the following permission to the app's AndroidManifest.xml file:

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

Note that in order to support Android 14 or newer, also the FOREGROUND_SERVICE_LOCATION permission is required.

In addition, if you want to keep the application running in background on Android devices on Android API level 29 or higher, you need to start a foreground service or similar. Remember that on Android requesting background locations is different than requesting a foreground location permission. Check the Android documentation for more details. Note that the positioning example app does not (yet) show how to support background positioning on Android 29 or higher.

Note

If your application targets Android API level 28 or lower, as long as your app already requests the permissions mentioned in the earlier Add the Required Permissions section, you don't need to make any changes to support background updates.

On iOS you can enable backround updates by adding the following key to the app's Info.plist file:

<key>UIBackgroundModes</key>
    <array>
        <string>location</string>
        <string>processing</string>
    </array>

The "processing" mode is needed for iOS versions 13.0 and above. When added also the following is needed:

<key>BGTaskSchedulerPermittedIdentifiers</key>
    <array>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
    </array>

Check the iOS documentation for more details.

Remember that the user needs to be requested for autorization. Check the Add the Required Permissions section for suggestions on how to request permissions from the user.

Once the autorization is cleared, you are all set. On iOS devices, you can enable or disable location updates in the background with the LocationEngine.setBackgroundLocationAllowed() method. Also on iOS devices, you can set the visibility of the application's background location indicator with the method LocationEngine.setBackgroundLocationIndicatorVisible().

The final check for iOS devices would be to ensure that the location updates won't pause when the device is stationary by passing false to the LocationEngine.setPauseLocationUpdatesAutomatically() method.

Note

setBackgroundLocationAllowed() and setBackgroundLocationIndicatorVisible() will return LocationEngineStatus.notAllowed if the application does not have background location capabilities enabled. Otherwise, LocationEngineStatus.ok will be returned.

On Android platforms, setBackgroundLocationAllowed(), setBackgroundLocationIndicatorVisible() and setPauseLocationUpdatesAutomatically() will return LocationEngineStatus.notSupported.

In addition, it is recommended to listen to the AppLifecycleState when you have background updates enabled.

Specify location options on android devices

If you are targeting Android devices and want more control over what options are taken into account when generating the locations, you can create a LocationOptions object, configure it to your liking, and start the engine by calling the startWithLocationOptions(locationOptions) method.

// ...

// Create a new LocationOptions object. By default all options are enabled.
LocationOptions locationOptions = LocationOptions();

// Use WiFi and satellite (GNSS) positioning only.
locationOptions.wifiPositioningOptions.enabled = true;
locationOptions.satellitePositioningOptions.enabled = true;
locationOptions.sensorOptions.enabled = false;
locationOptions.cellularPositioningOptions.enabled = false;

// Receive a location approximately every minute, but not more often than every 30 seconds.
locationOptions.notificationOptions.smallestIntervalMilliseconds = 30000;
locationOptions.notificationOptions.desiredIntervalMilliseconds = 60000;

_locationEngine.startWithLocationOptions(locationOptions);

// ...

Note

On iOS plaftorms, startWithLocationOptions() will return LocationEngineStatus.notSupported.

The table below shows an overview of the available LocationAccuracy modes, and how they are internally translated to LocationOptions in Android and to CLLocationAccuracy modes in iOS:

LocationAccuracy LocationOptions (Android) CLLocationAccuracy (iOS)
BEST_AVAILABLE cellularPositioningOptions.enabled = true
satellitePositioningOptions.enabled = true
wifiPositioningOptions.enabled = true
sensorOptions.enabled = true
notificationOptions.desired_interval_millisec = 30000 (30s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
kCLLocationAccuracyBest
NAVIGATION cellularPositioningOptions.enabled = false
satellitePositioningOptions.enabled = true
wifiPositioningOptions.enabled = true
sensorOptions.enabled = true
notificationOptions.desired_interval_millisec = 1000 (1s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
kCLLocationAccuracyBestForNavigation
SUB_METER_NAVIGATION cellularPositioningOptions.enabled = false
satellitePositioningOptions.enabled = true
satellitePositioningOptions.hdEnabled = true
wifiPositioningOptions.enabled = true
sensorOptions.enabled = true
notificationOptions.desired_interval_millisec = 1000 (1s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
N/A
TENS_OF_METERS cellularPositioningOptions.enabled = false
satellitePositioningOptions.enabled = false
wifiPositioningOptions.enabled = true
sensorOptions.enabled = true
notificationOptions.desired_interval_millisec = 30000 (30s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
kCLLocationAccuracyNearestTenMeters
HUNDREDS_OF_METERS cellularPositioningOptions.enabled = true
satellitePositioningOptions.enabled = false
wifiPositioningOptions.enabled = true
sensorOptions.enabled = false
notificationOptions.desired_interval_millisec = 30000 (30s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
kCLLocationAccuracyHundredMeters
KILOMETERS cellularPositioningOptions.enabled = true
satellitePositioningOptions.enabled = false
wifiPositioningOptions.enabled = false
sensorOptions.enabled = false
notificationOptions.desired_interval_millisec = 30000 (30s)
notificationOptions.smallest_interval_millisec = 1000 (1s)
kCLLocationAccuracyThreeKilometers

Note

The desired interval is not guaranteed by the LocationEngine, so it is possible that the locations will be delivered more or less often. The smallest interval, on the other hand, guarantees that the locations are not provided more often than the defined value.

Sub-meter navigation with Android devices

If you are targeting Android devices, starting the LocationEngine with LocationAccuracy.subMeterNavigation mode will enable HERE HD GNSS positioning. The HD GNSS (High Definition Global Navigation Satellite System) feature will allow high definition positioning for various use cases from lane assistance and turn-by-turn guidance to augmented reality. HD GNSS is a cloud-based solution that enables mass market devices to achieve sub-meter accuracy across the globe.

HD GNSS has special requirements for used Android devices. For this feature to work, Android version of the device has to be at least 12 (API 31). More specifically, device must support the following:

  • Raw GNSS measurements
  • GNSS carrier phase measurements (ADR)
  • Dual frequency GNSS receiver (L5)

See also Android documentation for more details.

It is the responsibility of the user to ensure conditions above hold with the used device. If not, the desired accuracy level may not be reached.

Conditions above do hold for some Android devices with earlier versions also. It is possible to successfully use LocationAccuracy.SUB_METER_NAVIGATION mode with some of these versions but this should only be done for development and testing purposes.

Example comparison of ground track accuracy of traditional GNSS solution (left) and HERE HD GNSS solution (right).

This feature is not available by default, but has to be separately activated. Users require credentials to gain access to HERE HD GNSS correction data. Contact us for more details.

Note

Even if LocationAccuracy.SUB_METER_NAVIGATION mode is used, it does not ensure that it will be used in every case, but fallback to other positioning sources and technologies may occur. Typical reasons for this include device not having necessary capabilities, use in environments which can be considered urban where GNSS measurements have lower quality, or when the credentials are not enabled for this feature.

Access accuracy information from a location

The horizontalAccuracyInMeters field, which is present in the Location object, also known as the "radius of uncertainty", provides an estimate of the area within which the true geographic coordinates are likely to lie with a 68% probability. This is used to draw a halo indicator around the current location. The illustration below depicts the inner green circle as the location.coordinates and the surrounding circle as the accuracy circle with a radius of horizontalAccuracyInMeters. The true geographical coordinates may lie inside (68%) or outside (32%) the accuracy circle.

Illustration: Radius of horizontal uncertainty and vertical uncertainty.

Likewise, in the case of altitude, if the verticalAccuracyInMeters value is 10 meters, this indicates that the actual altitude is expected to fall within a range of altitude - 10m to altitude + 10m with a probability of 68%. Other accuracy values, like bearingAccuracyInDegrees and speedAccuracyInMetersPerSecond will follow the same rule: a smaller uncertainty results in a better accuracy.

Note

On Android devices, the coordinates.altitude value is given in relation to the WGS 84 reference ellipsoid. On iOS devices, the coordinates.altitude value is given in relation to the mean sea level instead.

Achieving probabilities other than 68% (CEP68)

What if the given probability of 68% (CEP68) is not enough - is it possible to achieve an accuracy of 99%? Yes, it is: Since the given circular error probability (CEP) follows a chi-squared distribution with two degrees-of-freedom, it is easy to calculate the desired probability based on the following formulas:

Probability Radius of Uncertainty
50% CEP50 = 0.78 x CEP68
60% CEP60 = 0.90 x CEP68
70% CEP70 = 1.03 x CEP68
80% CEP80 = 1.19 x CEP68
90% CEP90 = 1.42 x CEP68
95% CEP95 = 1.62 x CEP68
99% CEP99 = 2.01 x CEP68

The table above can be used to visualize various probability levels for a halo indicator on the map. For example, if the horizontal accuracy is 20 meters, you can (roughly) double the radius to achieve a probability of 99%. The accuracy value is always given as CEP68, that means:

CEP99 = 2.01 x CEP68 = 2.01 x 20m = 40.2m

Now you can draw a radius of 40.2 meters around the found location - and with a probability of 99%, the real location will lie within that circle. On the other hand, the probability for a radius of 0 meters is 0%.

Using the HERE SDK location features requires you to show the HERE SDK consent dialog in your application as described above. Users must be able to see their current consent decision and to revoke any previous consent decision - otherwise, you are not allowed to use the HERE SDK location features and you must refer to the platform location APIs instead.

Note

Note that this requirement only applies to Android applications, as currently there is no data collection in iOS. This might change in the future.

Tutorial: Show your current location on a map

A LocationIndicator is used for representing device's current location on map. Before the indicator is updated with a current location value, a default Location is set, which can be the last known location - or just any place the user should see before the first location update arrives. By default, the horizontal accuracy is visualized with a MapCircle that has a radius of horizontalAccuracyInMeters.

//Default start-up location.
static final GeoCoordinates _defaultGeoCoordinates = GeoCoordinates(52.530932, 13.384915);

// LocationIndicator object to represent device's current location.
LocationIndicator _locationIndicator;

// ...

void _addMyLocationToMap(Location myLocation) {
  if (_locationIndicator != null) {
    return;
  }
  // Set-up location indicator.
  _locationIndicator = LocationIndicator();
  // Enable a halo to indicate the horizontal accuracy.
  _locationIndicator!.isAccuracyVisualized = true;
  _locationIndicator!.locationIndicatorStyle = LocationIndicatorIndicatorStyle.pedestrian;
  _locationIndicator!.updateLocation(myLocation);
  _locationIndicator!.enable(_hereMapController!);

  // Point camera at given location.
  MapMeasure mapMeasureZoom = MapMeasure(MapMeasureKind.distance, _cameraDistanceInMeters);
  _hereMapController!.camera.lookAtPointWithMeasure(
    myLocation.coordinates,
    mapMeasureZoom,
  );

  // Update state's location.
  setState(() {
    _location = myLocation;
  });
}

// ...

    Location? location = _locationEngine.lastKnownLocation;

    if (location == null) {
        // No last known location, use default instead.
        location = Location.withCoordinates(_defaultGeoCoordinates);
        location.time = DateTime.now();
    }

    _addMyLocationToMap(location);

// ...

void _updateMyLocationOnMap(Location myLocation) {
    if (_locationIndicator == null) {
      return;
    }

    // Update location indicator's location.
    _locationIndicator?.updateLocation(myLocation);

    // Point camera at given location.
    _hereMapController?.camera.lookAtPoint(myLocation.coordinates);

    // Update state's location.
    setState(() {
        _location = myLocation;
    });
}

Screenshot: Location indicator showing current location on map.

As shown in the implementation above, you can pass the Location object to the location indicator by calling updateLocation(). In this example, the goal is to track the user's current location - therefore, the map viewport's center location is updated as well.

Note

A more comprehensive tutorial that shows how to build an app with HERE Positioning and GPX tracking features for Android platform and iOS platform is available with complete code on GitHub.

results matching ""

    No results matching ""