Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add location manager for free drive #2410

Merged
merged 19 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
### User location

* Improved the accuracy of location tracking and off-route detection. ([#2319](https://github.com/mapbox/mapbox-navigation-ios/pull/2319))
* Added the `PassiveLocationManager` class for use with the `MGLMapView.locationManager` property. Unlike `CLLocationManager`, this class causes the map view to display user locations snapped to the road network, just like during turn-by-turn navigation. To receive these locations without an `MGLMapView`, use the `PassiveLocationDataSource` class and implement the `PassiveLocationDataSourceDelegate.passiveLocationDataSource(_:didUpdateLocation:rawLocation:)` method or observe `Notification.Name.passiveLocationDataSourceDidUpdate` notifications. ([#2410](https://github.com/mapbox/mapbox-navigation-ios/pull/2410))
* Fixed an issue where location tracking would pause at the beginning of a route after setting `RouteOptions.shapeFormat` to `RouteShapeFormat.polyline` or `RouteShapeFormat.geoJSON`. Note that you most likely do not need to override the default value of `RouteShapeFormat.polyline6`: this is the least bandwidth-intensive format, and `Route.shape` and `RouteStep.shape` are set to `LineString`s regardless. ([#2319](https://github.com/mapbox/mapbox-navigation-ios/pull/2319))
* Fixed an issue where various delegate methods omitted `CLLocation.courseAccuracy` and `CLLocation.speedAccuracy` properties from passed-in `CLLocation` objects when using `RouteController`, even when these properties are provided by Core Location on iOS 13.4 and above. ([#2417](https://github.com/mapbox/mapbox-navigation-ios/pull/2417))
* Fixed issues where the user puck would sometimes drift away from the route line even though the user was following the route. ([#2412](https://github.com/mapbox/mapbox-navigation-ios/pull/2412), [#2417](https://github.com/mapbox/mapbox-navigation-ios/pull/2417))
Expand Down
80 changes: 76 additions & 4 deletions Example/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ class ViewController: UIViewController {
@IBOutlet weak var clearMap: UIButton!
@IBOutlet weak var bottomBarBackground: UIView!

var trackPolyline: MGLPolyline?
var rawTrackPolyline: MGLPolyline?

// MARK: Properties
var mapView: NavigationMapView? {
didSet {
oldValue?.removeFromSuperview()
if let mapView = oldValue {
uninstall(mapView)
}
if let mapView = mapView {
configureMapView(mapView)
view.insertSubview(mapView, belowSubview: longPressHintView)
Expand Down Expand Up @@ -85,6 +90,12 @@ class ViewController: UIViewController {
}
}

deinit {
if let mapView = mapView {
uninstall(mapView)
}
}

// MARK: - Lifecycle Methods

override func viewDidLoad() {
Expand Down Expand Up @@ -343,7 +354,6 @@ class ViewController: UIViewController {
present(navigationViewController, animated: true) { [weak self] in
completion?()

self?.mapView?.removeFromSuperview()
self?.mapView = nil
}
}
Expand Down Expand Up @@ -375,12 +385,20 @@ class ViewController: UIViewController {
mapView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
mapView.delegate = self
mapView.navigationMapViewDelegate = self
mapView.userTrackingMode = .follow
mapView.logoView.isHidden = true

let singleTap = UILongPressGestureRecognizer(target: self, action: #selector(didLongPress(tap:)))
mapView.gestureRecognizers?.filter({ $0 is UILongPressGestureRecognizer }).forEach(singleTap.require(toFail:))
mapView.addGestureRecognizer(singleTap)

trackLocations(mapView: mapView)
mapView.showsUserLocation = true
mapView.userTrackingMode = .followWithHeading
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map view isn’t showing the heading indicator for some reason. I think it has to do with the location manager only delivering location updates and not heading updates. In any case, I don’t think we want the map view to rotate with the user’s heading or course, because frequent rotation makes the map much less useful for choosing a destination.

}

func uninstall(_ mapView: NavigationMapView) {
NotificationCenter.default.removeObserver(self, name: .passiveLocationDataSourceDidUpdate, object: nil)
mapView.removeFromSuperview()
}
}

Expand All @@ -398,6 +416,20 @@ extension ViewController: MGLMapViewDelegate {
self.mapView?.showWaypoints(on: currentRoute)
}
}

func mapView(_ mapView: MGLMapView, strokeColorForShapeAnnotation annotation: MGLShape) -> UIColor {
if annotation == trackPolyline {
return .darkGray
}
if annotation == rawTrackPolyline {
return .lightGray
}
return .black
}

func mapView(_ mapView: MGLMapView, lineWidthForPolylineAnnotation annotation: MGLPolyline) -> CGFloat {
return annotation == trackPolyline || annotation == rawTrackPolyline ? 4 : 1
}
}

// MARK: - NavigationMapViewDelegate
Expand Down Expand Up @@ -529,7 +561,7 @@ extension ViewController: NavigationViewControllerDelegate {
}
}

// Mark: VisualInstructionDelegate
// MARK: VisualInstructionDelegate
extension ViewController: VisualInstructionDelegate {
func label(_ label: InstructionLabel, willPresent instruction: VisualInstruction, as presented: NSAttributedString) -> NSAttributedString? {
// Uncomment to mutate the instruction shown in the top instruction banner
Expand All @@ -541,3 +573,43 @@ extension ViewController: VisualInstructionDelegate {
return presented
}
}

// MARK: Free driving
extension ViewController {
func trackLocations(mapView: NavigationMapView) {
let dataSource = PassiveLocationDataSource(directions: Settings.directions)
let locationManager = PassiveLocationManager(dataSource: dataSource)
mapView.locationManager = locationManager
Comment on lines +580 to +582
Copy link
Contributor

@1ec5 1ec5 Aug 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the bare minimum necessary to integrate free-driving localization into an application’s pre-navigation map view. The rest of the changes in this file are related to a nifty but inessential feature that plots the user’s raw and idealized path on the map. (You don’t need to pass in a custom Directions object if the default is OK.)


NotificationCenter.default.addObserver(self, selector: #selector(didUpdatePassiveLocation), name: .passiveLocationDataSourceDidUpdate, object: dataSource)

trackPolyline = nil
rawTrackPolyline = nil
}

@objc func didUpdatePassiveLocation(_ notification: Notification) {
if let roadName = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.roadNameKey] as? String {
title = roadName
}

if let location = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.locationKey] as? CLLocation {
if trackPolyline == nil {
trackPolyline = MGLPolyline()
}

var coordinates: [CLLocationCoordinate2D] = [location.coordinate]
trackPolyline?.appendCoordinates(&coordinates, count: UInt(coordinates.count))
}

if let rawLocation = notification.userInfo?[PassiveLocationDataSource.NotificationUserInfoKey.rawLocationKey] as? CLLocation {
if rawTrackPolyline == nil {
rawTrackPolyline = MGLPolyline()
}

var coordinates: [CLLocationCoordinate2D] = [rawLocation.coordinate]
rawTrackPolyline?.appendCoordinates(&coordinates, count: UInt(coordinates.count))
}

mapView?.addAnnotations([rawTrackPolyline!, trackPolyline!])
}
}
11 changes: 11 additions & 0 deletions MapboxCoreNavigation/BundleAdditions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ extension Bundle {
return Bundle(for: RouteController.self)
}

/**
The Mapbox Navigation framework bundle, if installed.
*/
public class var mapboxNavigation: Bundle? {
// Assumption: MapboxNavigation.framework includes NavigationViewController and exposes it to the Objective-C runtime as MapboxNavigation.NavigationViewController.
guard let NavigationViewController = NSClassFromString("MapboxNavigation.NavigationViewController") else {
Comment on lines +43 to +44
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We’ve been making this assumption since #2221. This change just moves it somewhere more accessible to non-events-related code.

return nil
}
return Bundle(for: NavigationViewController)
}

public func ensureSuggestedTileURLExists() -> Bool {
guard let tilePath = suggestedTileURL else { return false }
try? FileManager.default.createDirectory(at: tilePath, withIntermediateDirectories: true, attributes: nil)
Expand Down
50 changes: 48 additions & 2 deletions MapboxCoreNavigation/CoreConstants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,21 @@ public var RouteControllerIncorrectCourseMultiplier: Int = 4
public var RouteControllerMaximumSpeedForUsingCurrentStep: CLLocationSpeed = 1

public extension Notification.Name {
/**
Posted when `PassiveLocationDataSource` receives a user location update representing movement along the expected route.

The user info dictionary contains the keys `PassiveLocationDataSource.NotificationUserInfoKey.locationKey`, `PassiveLocationDataSource.NotificationUserInfoKey.rawLocationKey`, `PassiveLocationDataSource.NotificationUserInfoKey.matchesKey`, and `PassiveLocationDataSource.NotificationUserInfoKey.roadNameKey`.

- seealso: `routeControllerProgressDidUpdate`
*/
static let passiveLocationDataSourceDidUpdate: Notification.Name = .init(rawValue: "PassiveLocationDataSourceDidUpdate")

/**
Posted when `RouteController` receives a user location update representing movement along the expected route.

The user info dictionary contains the keys `RouteController.NotificationUserInfoKey.routeProgressKey`, `RouteController.NotificationUserInfoKey.locationKey`, and `RouteController.NotificationUserInfoKey.rawLocationKey`.

- seealso: `passiveLocationDataSourceDidUpdate`
*/
static let routeControllerProgressDidChange: Notification.Name = .init(rawValue: "RouteControllerProgressDidChange")

Expand Down Expand Up @@ -164,8 +175,7 @@ public extension Notification.Name {

extension RouteController {
/**
Keys in the user info dictionaries of various notifications posted by instances
of `RouteController`.
Keys in the user info dictionaries of various notifications posted by instances of `RouteController`.
*/
public struct NotificationUserInfoKey: Hashable, Equatable, RawRepresentable {
public typealias RawValue = String
Expand Down Expand Up @@ -213,3 +223,39 @@ extension RouteController {
}
}

extension PassiveLocationDataSource {
/**
Keys in the user info dictionaries of various notifications posted by instances of `PassiveLocationDataSource`.
*/
public struct NotificationUserInfoKey: Hashable, Equatable, RawRepresentable {
public typealias RawValue = String

public var rawValue: String

public init(rawValue: String) {
self.rawValue = rawValue
}

/**
A key in the user info dictionary of a `Notification.Name.passiveLocationDataSourceDidUpdate` notification. The corresponding value is a `CLLocation` object representing the current idealized user location.
*/
public static let locationKey: NotificationUserInfoKey = .init(rawValue: "location")

/**
A key in the user info dictionary of a `Notification.Name.passiveLocationDataSourceDidUpdate` notification. The corresponding value is a `CLLocation` object representing the current raw user location.
*/
public static let rawLocationKey: NotificationUserInfoKey = .init(rawValue: "rawLocation")

/**
A key in the user info dictionary of a `Notification.Name.passiveLocationDataSourceDidUpdate` notification. The corresponding value is an array of `Match` objects representing possible matches against the road network.
*/
public static let matchesKey: NotificationUserInfoKey = .init(rawValue: "matches")

/**
A key in the user info dictionary of a `Notification.Name.passiveLocationDataSourceDidUpdate` notification. The corresponding value is a string representing the name of the road the user is currently traveling on.

- seealso: `WayNameView`
*/
public static let roadNameKey: NotificationUserInfoKey = .init(rawValue: "roadName")
}
}
5 changes: 5 additions & 0 deletions MapboxCoreNavigation/MBXPeerWrapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@interface MBXPeerWrapper

@end
1ec5 marked this conversation as resolved.
Show resolved Hide resolved
2 changes: 2 additions & 0 deletions MapboxCoreNavigation/MapboxCoreNavigation.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ FOUNDATION_EXPORT double MapboxCoreNavigationVersionNumber;
FOUNDATION_EXPORT const unsigned char MapboxCoreNavigationVersionString[];

#import "MBXAccounts+CoreNavigationAdditions.h"

#import <MapboxCoreNavigation/MBXPeerWrapper.h>
3 changes: 1 addition & 2 deletions MapboxCoreNavigation/NavigationEventsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ open class NavigationEventsManager {
Indicates whether the application depends on MapboxNavigation in addition to MapboxCoreNavigation.
*/
var usesDefaultUserInterface = {
// Assumption: MapboxNavigation.framework includes NavigationViewController and exposes it to the Objective-C runtime as MapboxNavigation.NavigationViewController.
return NSClassFromString("MapboxNavigation.NavigationViewController") != nil
return Bundle.mapboxNavigation != nil
}()

/// :nodoc: the internal lower-level mobile events manager is an implementation detail which should not be manipulated directly
Expand Down
Loading