Skip to main content
APIs 15 min read

Building a Hiking App with HERE SDK for iOS

Way to Chandrashilla trek in the Garhwal Himalayan range, 5000 m AMSL

A Digital Diary for Your Adventure Trips

During my recent trip to the Himalayas, I embarked on a challenging trek. As an adventure enthusiast, we all know the excitement of documenting our hiking and trekking trips to show our friends and family. While traditional methods such as cameras can provide a visual record of these experiences, they do not capture the exact path taken. This inspired me to create an iOS application that would allow me to capture the essence of the journey and relive it later with loved ones. In this blog post, we'll use the HERE SDK for iOS (Navigate Edition) to record and visualize our audacious trips.

The HERE SDK offers a scalable and easily integratable location-based solution with positioning, geocoding, routing, directions, guidance and more. Our app will demonstrate the usage of the following features of the HERE SDK:

  • Map display: Shows the user's current location and other relevant information.
  • Location tracking: Uses the device's GPS sensor to track the user's location during a hike, allowing for accurate distance.
  • GPX feature: Allows to record, store and retrieve multiple hikes.

 

You can use this app as a starting point to create your own customized applications that leverages the HERE SDK's many features and functionalities. Furthermore, the HERE SDK is shipped with readymade code that you can find on HERE GitHub.

Note that our focus will be on recording GPS signals, rather than counting steps because we want to see our hiking path on the HERE map.

Now, let's delve into the use of the HERE SDK to build our HikingDiary app.

Get and Show Locations

With the code snippets from the HERE SDK found on this GitHub repo, we can easily copy & paste the code to determine our location by integrating the HEREPositioningProvider into our app. TheHEREPositioningProvider utilizes the LocationEngine provided by the HERE SDK that uses the iOS platform positioning, and provides high-precision location data from a variety of sources such as GPS, network location providers, other Global Navigation Satellite System (GNSS) receivers.

As a next step, we can integrate the copied HEREPositioningProvider by inheriting the LocationDelegate in our main class, we named it HikingApp.swift, and create a HEREPositioningProvider instance in that main class to get started:

Copied
        // A class to receive location events from the device.
private let herePositioningProvider = HEREPositioningProvider()

// Sets delegate to receive locations from HERE Positioning.
herePositioningProvider.startLocating(locationDelegate: self, accuracy: .navigation)

...

// Conform to LocationDelegate protocol.
func onLocationUpdated(_ location: heresdk.Location) {
  ...
}
  

Do not forget to implement the onLocationUpdated(location: Location) method that conforms to LocationDelegate protocol. This method is invoked on the main thread and called each time a new Location is available.

Note: You can set the accuracy precision of the location updates via accuracy parameter. The navigation setting is the highest precision possible.

You can find the final implementation of our HikingApp class on GitHub.

Add the Required Permissions

Location tracking raises some privacy concerns, as it may reveal sensitive information about the user's whereabouts and activities. To address these concerns, many mobile platforms require apps to obtain explicit user consent before accessing location data and provide options to control the frequency and granularity of the location tracking. It is important for app developers to handle location data responsibly and in accordance with the privacy regulations and best practices. Hence, before you start using the LocationEngine in your app, you need to add the required permissions to the app's Info.plist file:

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

You can find the resulting Info.plist file on GitHub.

Enable Background Updates

Whenever I go to the Himalayas, I usually put my device into my pocket and leave it there. Therefore, to continue tracking our hike, the app needs to enable background updates. For this, we need to set locationEngine.setBackgroundLocationAllowed to true and enable such capability by adding the following key to the app's Info.plist file:

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

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

Check Apple's iOS documentation for more details.

Also, in the copied HEREPositioningProvider add these lines, as by default, background updates are not enabled:

Copied
        // Allow background location updates.
_ = locationEngine.setBackgroundLocationAllowed(allowed: true)
  

Alternatively, you can enable location permissions from the Xcode's interface, under the top project level. Go to Signing & Capabilities > Targets > Background Modes and check for "Location updates" and "Background processing".

Now that we are able to receive locations, we have to check the accuracy of the location:

The Location object that we receive from onLocationUpdated describes the location of the user in the world at a given time and it is just an estimation of the true geographic coordinates. Even if you yourself are stationary, the GNSS satellites are not stationary but travel at a huge speed in the space, which may cause the location estimate to move around. For example, when meditating or having a meal while trekking, the location estimate will vary around my actual location. In such scenarios, the app should not update positions because this way you can travel miles even sitting at home.

Below illustration visualizes the problem with the fluctuation of the received GPS signals:

 Illustration: GPS signal visualization.

The hiking man denotes the location of the user (moving or non-moving). The circle surrounding the man is the Accuracy Circle whose radius is defined by horizontalAccuracyInMeters at any time t1. The blue circles are examples of possible locations at any time t1 with 68% probability of lying inside the horizontal accuracy circle, whereas, the red circles are examples of the possible locations at any time t1 with 32% probability of lying outside the accuracy circle. For example, if the horizontalAccuracyInMeters is 10 meters then it is 68% likely that the true geographic coordinates can lie within the radius of 10 meters.

With a naive implementation, we could just accept the locations that are below a certain accuracy threshold.

This can lead to imprecise distance calculation, as illustrated below:

Illustration: Problems with distance calculation.

Illustration: Problems with distance calculation.

 

The black lines are the calculated distances based on the GPS signals and the blue lines are the actual distances that are impossible to determine, unless we roll out a rope behind us and track the real path.

How to solve this problem?

Create Location Filtering Algorithm

To solve the above problems, we need to create a location filtering algorithm that accepts locations only when moving. For this, we will create a new DistanceAccuracyLocationFilter class and provide a filtering strategy based on the horizontal accuracy of the location estimate and the distance to our previous location estimate.

For this, we need two types of filters:

  1. DistanceFilter: Filters the locations based on a distance threshold.
  2. AccuracyFilter: Filters the locations based on the horizontal accuracy of the location estimate, and only includes the readings with a certain level of accuracy. To create this filter we will use the horizontalAccuracyInMeters property which is found in the Location object provided by the HERE SDK. It gives the estimated horizontal accuracy in meters and tells us that the true geographic coordinates will lie within this radius of uncertainty with a probability of 68% (see first illustration above).

 

Let's look how we can implement such a distance filter. Firstly, we need to define a distanceThresholdInMeters property, let's say 20 meters. We will only accept the GPS signal if the estimated location is beyond the distanceThresholdInMeters from the last accepted location. Below illustration visualizes the idea of our planned mechanism:

Illustration: Distance threshold visualization.

Illustration: Distance threshold visualization.

 

Each circle represents a single location estimate with a varying accuracy circle. The crossed circles are below the distance threshold and therefore discarded.

On top of the distance filter, we also need to consider the accuracy of each GPS signal (see above). For this we define a accuracyRadiusThresholdInMeters property. Let's say it is 10 meters. Therefore, each GPS signal with a higher value than accuracyRadiusThresholdInMeters will be filtered out. In our first illustration above, this would mean that all blue GPS signals are accepted and the red ones are discarded.

Now, we are ready to go for the implementation of our algorithm using the two filter mechanisms:

Copied
        protocol LocationFilterStrategy {
    func checkIfLocationCanBeUsed(_ location: Location) -> Bool
}

class DistanceAccuracyLocationFilter: LocationFilterStrategy {
    // These two parameters define if incoming location updates are considered to be good enough.
    // In the field, the GPS signal can be very unreliable, so we need to filter out inaccurate signals.
    static let accuracyRadiusThresholdInMeters = 10.0
    static let distanceThresholdInMeters = 15.0
    private var lastAcceptedGeoCoordinates: GeoCoordinates?

    func checkIfLocationCanBeUsed(_ location: Location) -> Bool {
        if isAccuracyGoodEnough(location) && isDistanceFarEnough(location) {
            lastAcceptedGeoCoordinates = location.coordinates
            return true
        }
        return false
    }

    // Checks if the accuracy of the received GPS signal is good enough.
    private func isAccuracyGoodEnough(_ location: Location) -> Bool {
        guard let horizontalAccuracyInMeters = location.horizontalAccuracyInMeters else {
            return false
        }

        // If the location lies within the radius of accuracyCircleRadiusInMetersThreshold then we accept it.
        if horizontalAccuracyInMeters <= DistanceAccuracyLocationFilter.accuracyRadiusThresholdInMeters {
            return true
        }
        return false
    }

    // Checks if last accepted location is farther away than xx meters.
    // If it is, the new location will be accepted.
    // This way we can filter out signals that are caused by a non-moving user.
    private func isDistanceFarEnough(_ location: Location) -> Bool {
        guard let lastAcceptedGeoCoordinates = lastAcceptedGeoCoordinates else {
            // We always accept the first location.
            lastAcceptedGeoCoordinates = location.coordinates
            return true
        }

        let distance = location.coordinates.distance(to: lastAcceptedGeoCoordinates)
        if distance >= DistanceAccuracyLocationFilter.distanceThresholdInMeters {
            return true
        }
        return false
    }
}

  

The DistanceAccuracyLocationFilter algorithm can be also found on the HERE SDK GitHub repo.

Now, we can create an instance of DistanceAccuracyLocationFilter and filter out the incoming location signals and update the locations on the map:

Copied
        private var locationFilter: LocationFilterStrategy

...

// Filter out undesired location signals.
locationFilter = DistanceAccuracyLocationFilter()

...

func onLocationUpdated(_ location: heresdk.Location) {
    if locationFilter.checkIfLocationCanBeUsed(location) {
      // Use the location.
    }
}
  

You can also create your own location filter algorithm by adopting the LocationFilterStrategy protocol. For example, you can implement a bypass filter like this:

Copied
        // The DefaultLocationFilter class implements the LocationFilterStrategy protocol and
// allows every location signal to pass inorder to visualize the raw GPS signals on the map.
class DefaultLocationFilter: LocationFilterStrategy {
    func checkIfLocationCanBeUsed(_ location: Location) -> Bool {
        return true
    }
}
  

With the LocationFilterStrategy you can customize the app to use different algorithms for location filtering without changing the core functionality of the app. Note that our filtering algorithm above is kept as simple as possible, it can be improved as per your needs.

Record Location Updates

Once we have the location updates, the next step is to record them. For this, we use a GPXTrackWriter code snippet from the HERE SDK GitHub that allows us to create a GPXTrack. This GPXTrack can be stored and loaded with the GPXDocument class. The GPXDocument contains all the GPX tracks and is saved in the GPX file format. Hence, once saved, it can be easily shared with other applications that understand the GPX file format.

GPX is a de facto standard data format for GPS data exchange, including waypoints, tracks, and routes. GPX files contain geographical information in XML format and are easy to process with various software programs and GPS devices.

The HERE SDK can help with GPX data by providing tools for reading, displaying, and manipulating GPX data in a variety of ways.

For recording the location updates, create an instance of GPXManager to manage our GPX operations:

Copied
        private var gpxTrackWriter = GPXTrackWriter()
private let gpxManager: GPXManager

// Create a GPXDocument file with named as myGPXDocument.
gpxManager = GPXManager(gpxDocumentFileName: "myGPXDocument")
  

Now, we can write location updates to a GPXTrack inside the onLocationUpdated() method:

Copied
        // Add location updates to a GPX track.
gpxTrackWriter.onLocationUpdated(location)
  

Finally, to store the hiking trip, we use this one-liner code that stores the GPX track in a GPXDocument:

Copied
        // Permanently store the trip on the device.
gpxManager.saveGPXTrack(gpxTrackWriter.track)
  

In order to load our trips, call:

Copied
        guard let gpxTrack = gpxManager.getGPXTrack(index: index) else {
    return
}
  

Note: You can record and store multiple hiking trips as multiple GPXTrack in a single GPXDocument.

Finalize the App

Now, in order to complete our app, we show the travelled path with a MapPolyline on the map. The implementation of a MapPolylinefollows the Map Items Guide that can be found in the Developer's Guide for the HERE SDK. It can be also seen in the HikingApp.swift class.

In order to extend the polyline during the trip, we take the latest list of coordinates from the gpxManager instance, since it already contains the latest location updates. With this list we can create a new geoPolyline instance that we set to our existing mapPolyline instance:

Copied
        // Update the polyline shape that shows the travelled path of the user.
mapPolyline.geometry = geoPolyline
  

This will immediately extend the polyline that is already shown on the map view.

Note that in this blog post, we did not show the app development process from scratch. To get a more complete picture, take a look at this Get Started guide.

You can find the complete Xcode project on GitHub.

Below you can see the finished app:

hiking diary app side by side

The HERE SDK provides various map schemes. By default, in our app we show the satellite map scheme. However, you can change the MapScheme and switch to a different map scheme. To know more about the available MapScheme options, you can refer to the API Reference of the HERE SDK.

Also, in our app, you can enable an outdoor raster layer on top of the mapview by sliding a switch at the top-right corner of the main view. Note that this is a 3rd party raster layer that can be suitable for hiking trips, as it shows height lines and more detailed paths. Here, we use an outdoor map from thunderforest.com. More information about custom raster layers can be found in the Developer's Guide for the HERE SDK.

Next Steps

There are multiple ways to enhance our little HikingDiary app. Below are some ideas:

  • Use the altitude property of the GeoCoordinates object to include a height profile of your trip.
  • Integrate more map schemes, for example, the HERE SDK offers a terrain scheme that provides hill shading.
  • Store your trips with the overall duration and timestamps where you took rests.
  • Include an export option of the stored GPX file to share it with other applications.

 

Since we released the app as an open-source project on GitHub, contributions are welcome! Feel free to submit pull requests with bug fixes, new features, and overall improvements.

Happy hiking!

abhishek kumar

Abhishek Kumar

Software Engineer II (R&D)

Have your say

Sign up for our newsletter

Why sign up:

  • Latest offers and discounts
  • Tailored content delivered weekly
  • Exclusive events
  • One click to unsubscribe