Flitsmeister Navigation SDK for iOS 是基于 Mapbox Navigation SDK v0.21 的分支构建的,该版本构建在 Mapbox Directions API (v0.23.0) 之上,并包含获取计时导航说明所需的所有逻辑。
使用此SDK,您可以在自己的iOS应用中实现逐段导航,同时托管自己的地图瓦片和方向API。
为什么要分叉
- Mapbox 决定在其导航SDK中添加一个闭源组件,并引入了一个非开源许可证。Flitsmeister 希望得到一个开源解决方案。
- Mapbox 决定在其SDK中添加遥测。我们无法在不调整源代码的情况下将其关闭。
- 我们希望在不需要向Mapbox支付每MAU费用且不使用Mapbox API密钥的情况下使用SDK。
所有问题均通过此SDK解决。
我们做了哪些更改
- 删除了EventManager及其所有引用,该管理器收集了我们不希望发送的遥测数据
- 从Mapbox SDK(版本4.3)切换到Maplibre Maps SDK(版本5.12.2)
- 在NavigationMapView构造函数中添加了可选的配置参数,用于自定义某些属性,例如路线颜色
入门指南
如果您希望在自己的项目中使用此库,必须遵循以下步骤
- 安装Carthage
- 打开终端
- [可选] 在M1 Mac上更改终端到bash:
chsh -s /bin/bash
- 安装Homebrew:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
- 安装Carthage:
brew install carthage
- 创建一个新的XCode项目
- 创建Cartfile
- 打开终端
- 更改位置到XCode项目的根目录:
cd path/to/Project
- 创建Cartfile:
touch Cartfile
- 新文件将被添加:
Cartfile
- 通过前往您的应用程序项目文件 -> 包依赖项 -> 点击 '+' -> https://github.com/maplibre/maplibre-gl-native-distribution -> 'Exact' 5.12.2 添加Maplibre Maps SPM(Swift Package Manager)依赖项
- 向Cartfile添加依赖
github "flitsmeister/flitsmeister-navigation-ios" ~> 1.0.6
- 构建框架
- 打开终端
- 更改位置到XCode项目的根目录:
cd path/to/Project
- 运行:
carthage bootstrap --platform iOS --use-xcframeworks
- 将添加新文件
- Cartfile.resolved = 表示已获取/构建的框架
- Carthage文件夹 = 包含所有构建的框架
- 将框架拖放到项目中:
TARGETS -> General -> 框架、库...
- 所有xcframeworks
- 向Info.plist添加属性
- MGLMapboxAccessToken / 字符串 /保留空白 = 确保SDK不会崩溃
- MGLMapboxAPIBaseURL / String / 添加url = 用于获取导航JSON的URL
- NSLocationWhenInUseUsageDescription / String / 添加描述 = 需要地理位置权限
- [可选] 当应用在设备上运行并遇到问题时:将
arm64
添加到PROJECT -> <项目名称> -> Build Settings -> Excluded Architecture Only
- 以示例代码为灵感
获取帮助
- 有bug要报告吗? 提交一个issue。如果可能,请包括Flitsmeister服务的版本、完整日志以及显示问题的项目。
- 有功能请求吗? 提交一个issue。告诉我们功能应该做什么以及为什么你需要这个功能。
示例代码
当前没有可用的演示应用。请查看Mapbox仓库或文档中的示例,特别是分叉版本。你可以尝试提供的演示应用,你需要在项目根目录下首先运行carthage update --platform iOS --use-xc-frameworks
。
为了看到地图或计算路线,你需要自己的地图瓦片和方向服务。
以下代码仅供参考
import Mapbox
import VietMapDirections
import VietMapCoreNavigation
import VietMapNavigation
class ViewController: UIViewController {
var navigationView: NavigationMapView?
// Keep `RouteController` in memory (class scope),
// otherwise location updates won't be triggered
public var mapboxRouteController: RouteController?
override func viewDidLoad() {
super.viewDidLoad()
let navigationView = NavigationMapView(
frame: .zero,
// Tile loading can take a while
styleURL: URL(string: "your style URL here"),
config: MNConfig())
self.navigationView = navigationView
view.addSubview(navigationView)
navigationView.translatesAutoresizingMaskIntoConstraints = false
navigationView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
navigationView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
navigationView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
navigationView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
let waypoints = [
CLLocation(latitude: 52.032407, longitude: 5.580310),
CLLocation(latitude: 51.768686, longitude: 4.6827956)
].map { Waypoint(location: $0) }
let options = NavigationRouteOptions(waypoints: waypoints, profileIdentifier: .automobileAvoidingTraffic)
options.shapeFormat = .polyline6
options.distanceMeasurementSystem = .metric
options.attributeOptions = []
print("[\(type(of:self))] Calculating routes with URL: \(Directions.shared.url(forCalculating: options))")
/// URL is based on the base URL in the Info.plist called `MGLMapboxAPIBaseURL`
/// - Note: Your routing provider could be strict about the user-agent of this app before allowing the call to work
Directions.shared.calculate(options) { (waypoints, routes, error) in
guard let route = routes?.first else { return }
let simulatedLocationManager = SimulatedLocationManager(route: route)
simulatedLocationManager.speedMultiplier = 20
let mapboxRouteController = RouteController(
along: route,
directions: Directions.shared,
locationManager: simulatedLocationManager)
self.mapboxRouteController = mapboxRouteController
mapboxRouteController.delegate = self
mapboxRouteController.resume()
NotificationCenter.default.addObserver(self, selector: #selector(self.didPassVisualInstructionPoint(notification:)), name: .routeControllerDidPassVisualInstructionPoint, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(self.didPassSpokenInstructionPoint(notification:)), name: .routeControllerDidPassSpokenInstructionPoint, object: nil)
navigationView.showRoutes([route], legIndex: 0)
}
}
}
// MARK: - RouteControllerDelegate
extension ViewController: RouteControllerDelegate {
@objc public func routeController(_ routeController: RouteController, didUpdate locations: [CLLocation]) {
let camera = MGLMapCamera(
lookingAtCenter: locations.first!.coordinate,
acrossDistance: 500,
pitch: 0,
heading: 0
)
navigationView?.setCamera(camera, animated: true)
}
@objc func didPassVisualInstructionPoint(notification: NSNotification) {
guard let currentVisualInstruction = currentStepProgress(from: notification)?.currentVisualInstruction else { return }
print(String(
format: "didPassVisualInstructionPoint primary text: %@ and secondary text: %@",
String(describing: currentVisualInstruction.primaryInstruction.text),
String(describing: currentVisualInstruction.secondaryInstruction?.text)))
}
@objc func didPassSpokenInstructionPoint(notification: NSNotification) {
guard let currentSpokenInstruction = currentStepProgress(from: notification)?.currentSpokenInstruction else { return }
print("didPassSpokenInstructionPoint text: \(currentSpokenInstruction.text)")
}
private func currentStepProgress(from notification: NSNotification) -> RouteStepProgress? {
let routeProgress = notification.userInfo?[RouteControllerNotificationUserInfoKey.routeProgressKey] as? RouteProgress
return routeProgress?.currentLegProgress.currentStepProgress
}
}
许可证
代码根据MIT和ISC许可证授权。ISC许可证的功能等同于MIT许可证。