Get Locations

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 uses the iOS plaftorm positioning in the background, and 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.

At a glance

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

  1. Add the required iOS permissions to your .plist file and request the permissions from the user.
  2. Create a LocationEngine and set at least one LocationUpdateDelegate.
  3. Start the LocationEngine once and set the desired accuracy level.
  4. Receive Location updates and handle them in your app.

Add the Required Permissions

Before you can start using the LocationEngine in your app, you will need to add the required permissions to the app's Info.plist file:

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

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. Prior to using the LocationEngine, it may be a good idea to check if the native location services are enabled. On most iOS devices, a user can navigate to Settings > Privacy > Location Services to make sure that the location services are on.

You can use the code snippet below to check the application's CLAuthorizationStatus and request the user authorization. Check the iOS documentation to find out more about Apple's CLAuthorizationStatus.

import CoreLocation

// ...

// Core location instance is needed for requesting location authorization.
private let locationManager = CLLocationManager()

// ...

private func startLocationUpdates()
{
    ensureLocationAuthorization()

    // ...

}

private func ensureLocationAuthorization() {
    // Get current location authorization status.
    let locationAuthorizationStatus = CLLocationManager.authorizationStatus()

    // Check authorization.
    switch locationAuthorizationStatus {
    case .notDetermined:
        // Not determined, request for authorization.
        locationManager.requestAlwaysAuthorization()
        break
    case .denied, .restricted:
        // Denied or restricted, request for user action.
        let alert = UIAlertController(title: "Location services are disabled", message: "Please enable location services in your device settings.", preferredStyle: .alert)
        let okAction = UIAlertAction(title: "OK", style: .default, handler: nil)
        alert.addAction(okAction)
        present(alert, animated: true, completion: nil)
        break
    case .authorizedAlways, .authorizedWhenInUse:
        // Authorized, ok to continue.
        break
    default:
        fatalError("Unknown location authorization status: \(locationAuthorizationStatus).")
    }

Create a LocationEngine

Creating a new LocationEngine is simple:

do {
    try locationEngine = LocationEngine()
} catch let engineInstantiationError {
    fatalError("Failed to initialize LocationEngine. Cause: \(engineInstantiationError)")
}

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 nil will be returned. This information will remain, so the last known location will also be available between application sessions.

if let myLastLocation = locationEngine.lastKnownLocation {
    // Log the last known location coordinates.
    print("Last known location: '%f', '%f'", myLastLocation.coordinates.latitude, myLastLocation.coordinates.longitude)
}

Note that 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 ensure that you will be notified of changes in the engine's status by conforming to the LocationStatusDelegate protocol and register it with the location engine's addLocationStatusDelegate() method. Check the API Reference for more information on the different statuses.

class PositioningExample: LocationStatusDelegate {

    func onStatusChanged(locationEngineStatus: LocationEngineStatus) {
        print("LocationEngineStatus: : \(locationEngineStatus)")
    }

    func onFeaturesNotAvailable(features: [LocationFeature]) {
        for feature in features {
            print("Feature not available: '%s'", String(describing: feature))
        }
    }

// ...

    locationEngine.addLocationStatusDelegate(locationStatusDelegate: self)

// ...

}

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

Additionally, through the delegate'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 conforming to the LocationUpdateDelegate protocol, which provides a callback that sends a notification once a new Location is detected. You can do so in a similar way as with the previously mentioned LocationStatusDelegate:

class PositioningExample: LocationUpdateDelegate {

    func onLocationUpdated(location: Location) {
        print("Location updated: \(location.coordinates)")
    }

// ...

    locationEngine.addLocationUpdateDelegate(locationUpdateDelegate: self)

// ...

}

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 nil. 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 LocationStatusDelegate and LocationUpdateDelegate as you need by calling the respective addLocationStatusDelegate() and addLocationUpdateDelegate methods.

Start and Stop Receiving Locations

You are now ready to call the LocationEngine's start() method by passing it it one of the pre-defined LocationAccuracy modes, as in the code snippet below:

class PositioningExample: LocationStatusDelegate, LocationUpdateDelegate {

// ...

    private func startLocating() {
        locationEngine.addLocationStatusDelegate(locationStatusDelegate: self)
        locationEngine.addLocationUpdateDelegate(locationUpdateDelegate: self)
        _ = locationEngine.start(locationAccuracy: .bestAvailable)
    }

// ...

}

Note that the start() method already returns a LocationStatus: Since we have set a LocationStatusDelegate in the line above, we do not need to consume the status right now and wait for the event instead.

LocationEngine uses the iOS platform positioning in the background to generate location updates. See the table below to understand how LocationAccuracy maps to iOS' own CLLocationAccuracy or check the API Reference for more information about all the available modes.

LocationAccuracy CLLocationAccuracy
bestAvailable kCLLocationAccuracyBest
navigation kCLLocationAccuracyBestForNavigation
tensOfMeters kCLLocationAccuracyNearestTenMeters
hundredsOfMeters kCLLocationAccuracyHundredMeters
kilometers kCLLocationAccuracyThreeKilometers
Table: Mapping of LocationAccuracy to CLLocationAccuracy.

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 delegates when they are no longer needed:

class PositioningExample: LocationStatusDelegate, LocationUpdateDelegate {

// ...

    public func stopLocating() {
        locationEngine.removeLocationUpdateDelegate(locationUpdateDelegate: self)
        locationEngine.removeLocationStatusDelegate(locationStatusDelegate: self)
        locationEngine.stop()
    }

// ...

}

Tutorial: Show your Current location on a Map

A custom map circle can be composed out of two MapPolygon instances: one representing the radius of uncertainty as a halo, and the other one representing the center. Before the circle 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.

//Default start-up location.
private static let defaultGeoCoordinates = GeoCoordinates(latitude: 52.520798, longitude: 13.409408)

// MapPolygon objects to represent a location circle.
private var locationAccuracyCircle: MapPolygon!
private var locationCenterCircle: MapPolygon!

// ...

private func addMyLocationToMap(geoCoordinates: GeoCoordinates, accuracyInMeters: Double) {
    // Transparent halo around the current location with radius of horizontal accuracy.
    let accuracyCircle = GeoCircle(center: geoCoordinates, radiusInMeters: accuracyInMeters)
    let accuracyPolygon = GeoPolygon(geoCircle: accuracyCircle)
    locationAccuracyCircle = MapPolygon(geometry: accuracyPolygon,
                                        color: PositioningExample.defaultAccuracyColor)
    mapView.mapScene.addMapPolygon(locationAccuracyCircle)
    // Solid circle on top of the current location.
    let centerCircle = GeoCircle(center: geoCoordinates, radiusInMeters: 1.0)
    let centerPolygon = GeoPolygon(geoCircle: centerCircle)
    locationCenterCircle = MapPolygon(geometry: centerPolygon,
                                      color: PositioningExample.defaultCenterColor)
    mapView.mapScene.addMapPolygon(locationCenterCircle)
    // Point camera to current location.
    mapCamera.lookAt(point: geoCoordinates,
                      distanceInMeters: PositioningExample.defaultCameraDistance)
}

private func updateMyLocationOnMap(geoCoordinates: GeoCoordinates, accuracyInMeters: Double) {
    // Update location accuracy circle.
    let accuracyCircle = GeoCircle(center: geoCoordinates, radiusInMeters: accuracyInMeters)
    locationAccuracyCircle.updateGeometry(GeoPolygon(geoCircle: accuracyCircle))
    // Update location center.
    let centerCircle = GeoCircle(center: geoCoordinates, radiusInMeters: 1.0)
    locationCenterCircle.updateGeometry(GeoPolygon(geoCircle: centerCircle))
    // Point camera to current location.
    mapCamera.lookAt(point: geoCoordinates)
}

// ...

if let lastLocation = locationEngine.lastKnownLocation {
    addMyLocationToMap(geoCoordinates: lastLocation.coordinates,
                       accuracyInMeters: lastLocation.horizontalAccuracyInMeters ?? 0.0)
} else {
    addMyLocationToMap(geoCoordinates: PositioningExample.defaultGeoCoordinates,
                       accuracyInMeters: 0.0)
}

// ...

func onLocationUpdated(location: Location) {
    updateMyLocationOnMap(geoCoordinates: location.coordinates,
                          accuracyInMeters: location.horizontalAccuracyInMeters ?? 1.0)
}

Screenshot: Showing a custom map marker indicating current location.

As shown in the implementation above, you can get the GeoCoordinates from the Location object and pass it to the custom map circle that represents your current location. In this example, the goal is to track the user's current location - therefore, the map viewport's center location is updated as well. See Map Items for more information on items such as polylines, polygons and markers you can add to the map.

results matching ""

    No results matching ""