Flitsmeister iOS 导航 SDK 基于 Mapbox Navigation SDK v0.21 的分支 (v0.21.0) 构建,该 SDK 是基于 Mapbox Directions API(v0.23.0)构建的,并包含获取时间导航指令所需的所有逻辑。
使用此 SDK,您可以在自己的 iOS 应用程序中实现逐步导航功能,同时托管自己的地图瓦片和 Directions API。
我们为何要分叉
- Mapbox 决定在其导航 SDK 中添加一个闭源组件,并引入了非开源许可证。Flitsmeister 希望有一个开源解决方案。
- Mapbox 决定在其 SDK 中添加遥测功能。我们无法在不调整源代码的情况下将其关闭。
- 我们想在不付给 Mapbox 每个MAU费用的情况下使用 SDK,并且不使用 Mapbox API 密钥。
所有问题都通过此 SDK 得到解决。
我们做了什么变更
- 移除了 EventManager 和所有相关引用,这个管理者收集了我们不希望发送的遥测数据
- 从 Mapbox SDK(版本 4.3)过渡到 Maplibre Maps SDK(版本 5.12.2)
- 在 NavigationMapView 构造函数中添加了可选的配置参数,用于自定义某些属性,例如路线颜色
入门指南
如果您想在项目中包含此 SDK,您必须遵循以下步骤
- 安装 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 / String / 留空 = 确保SDK不会崩溃
- MGLMapboxAPIBaseURL / String / 添加 URL = 用于获取导航 JSON 的 URL
- NSLocationWhenInUseUsageDescription / 字符串 / 添加描述 = 需要访问位置权限
- [可选] 当应用程序在设备上运行且有问题时:将
arm64
添加到项目 -> <项目名称> -> 编译设置 -> 仅排除架构
- 以示例代码为灵感
获取帮助
示例代码
目前没有可用的演示应用程序。请检查 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 许可证等效。