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

Initializes CarPlay #368

Merged
merged 8 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
16 changes: 16 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,12 +52,17 @@ let package = Package(
targets: [
"FerrostarMapLibreUI",
"FerrostarSwiftUI",
"FerrostarCarPlayUI",
] // TODO: Remove FerrostarSwiftUI from FerrostarMapLibreUI once we can fix the demo app swift package config (broken in Xcode 15.3)
),
.library(
name: "FerrostarSwiftUI",
targets: ["FerrostarSwiftUI"]
),
.library(
name: "FerrostarCarPlayUI",
targets: ["FerrostarCarPlayUI"]
),
],
dependencies: [
maplibreSwiftUIDSLPackage,
Expand All @@ -68,6 +73,17 @@ let package = Package(
],
targets: [
binaryTarget,
.target(
name: "FerrostarCarPlayUI",
dependencies: [
.target(name: "FerrostarCore"),
.target(name: "FerrostarSwiftUI"),
.target(name: "FerrostarMapLibreUI"),
.product(name: "MapLibreSwiftDSL", package: "swiftui-dsl"),
.product(name: "MapLibreSwiftUI", package: "swiftui-dsl"),
],
path: "apple/Sources/FerrostarCarPlayUI"
),
.target(
name: "FerrostarCore",
dependencies: [.target(name: "FerrostarCoreFFI")],
Expand Down
117 changes: 117 additions & 0 deletions apple/DemoApp/Demo/AppEnvironment.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import CoreLocation
import FerrostarCore
import FerrostarCoreFFI
import SwiftUI

enum AppDefaults {
static let initialLocation = CLLocation(latitude: 37.332726, longitude: -122.031790)
static let mapStyleURL =
URL(string: "https://tiles.stadiamaps.com/styles/outdoors.json?api_key=\(APIKeys.shared.stadiaMapsAPIKey)")!
}

enum DemoAppError: Error {
case noUserLocation
case noRoutes
case other(Error)
}

/// This is a shared core where ferrostar lives
class AppEnvironment: ObservableObject {
var locationProvider: LocationProviding
@Published var ferrostarCore: FerrostarCore
@Published var spokenInstructionObserver: SpokenInstructionObserver

let navigationDelegate = NavigationDelegate()

init(initialLocation: CLLocation = AppDefaults.initialLocation) {
let simulated = SimulatedLocationProvider(location: initialLocation)
simulated.warpFactor = 2
locationProvider = simulated

// Set up the a standard Apple AV Speech Synth.
spokenInstructionObserver = .initAVSpeechSynthesizer()

// Configure the navigation session.
// You have a lot of flexibility here based on your use case.
let config = SwiftNavigationControllerConfig(
stepAdvance: .relativeLineStringDistance(minimumHorizontalAccuracy: 32, automaticAdvanceDistance: 10),
routeDeviationTracking: .staticThreshold(minimumHorizontalAccuracy: 25, maxAcceptableDeviation: 20),
snappedLocationCourseFiltering: .snapToRoute
)

ferrostarCore = try! FerrostarCore(
valhallaEndpointUrl: URL(
string: "https://api.stadiamaps.com/route/v1?api_key=\(APIKeys.shared.stadiaMapsAPIKey)"
)!,
profile: "bicycle",
locationProvider: locationProvider,
navigationControllerConfig: config,
options: ["costing_options": ["bicycle": ["use_roads": 0.2]]],
// This is how you can set up annotation publishing;
// We provide "extended OSRM" support out of the box,
// but this is fully extendable!
annotation: AnnotationPublisher<ValhallaExtendedOSRMAnnotation>.valhallaExtendedOSRM()
)

// NOTE: Not all applications will need a delegate. Read the NavigationDelegate documentation for details.
ferrostarCore.delegate = navigationDelegate

// Initialize text-to-speech; note that this is NOT automatic.
// You must set a spokenInstructionObserver.
// Fortunately, this is pretty easy with the provided class
// backed by AVSpeechSynthesizer.
// You can customize the instance it further as needed,
// or replace with your own.
ferrostarCore.spokenInstructionObserver = spokenInstructionObserver
}

func getRoutes() async throws -> [Route] {
guard let userLocation = locationProvider.lastLocation else {
throw DemoAppError.noUserLocation
}

let waypoints = locations.map { Waypoint(
coordinate: GeographicCoordinate(lat: $0.coordinate.latitude, lng: $0.coordinate.longitude),
kind: .break
) }

let routes = try await ferrostarCore.getRoutes(initialLocation: userLocation,
waypoints: waypoints)

guard let route = routes.first else {
throw DemoAppError.noRoutes
}

print("DemoApp: successfully fetched routes")

if let simulated = locationProvider as? SimulatedLocationProvider {
// This configures the simulator to the desired route.
// The ferrostarCore.startNavigation will still start the location
// provider/simulator.
simulated
.lastLocation = UserLocation(clCoordinateLocation2D: route.geometry.first!.clLocationCoordinate2D)
print("DemoApp: setting initial location")
}

return routes
}

func startNavigation(route: Route) throws {
if let simulated = locationProvider as? SimulatedLocationProvider {
// This configures the simulator to the desired route.
// The ferrostarCore.startNavigation will still start the location
// provider/simulator.
try simulated.setSimulatedRoute(route, resampleDistance: 5)
print("DemoApp: setting route to be simulated")
}

// Starts the navigation state machine.
// It's worth having a look through the parameters,
// as most of the configuration happens here.
try ferrostarCore.startNavigation(route: route)
}

func stopNavigation() {
ferrostarCore.stopNavigation()
}
}
56 changes: 56 additions & 0 deletions apple/DemoApp/Demo/CarPlaySceneDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import CarPlay
import FerrostarCarPlayUI
import FerrostarCore
import SwiftUI
import UIKit

class CarPlaySceneDelegate: UIResponder, CPTemplateApplicationSceneDelegate {
// Get the AppDelegate associated with the SwiftUI App/@main as the type you defined it as.
@UIApplicationDelegateAdaptor(DemoAppDelegate.self) var appDelegate

private var ferrostarManager: FerrostarCarPlayManager?

func configure() {
guard ferrostarManager == nil else { return }

ferrostarManager = FerrostarCarPlayManager(
ferrostarCore: appDelegate.appEnvironment.ferrostarCore,
styleURL: AppDefaults.mapStyleURL
)
}

func templateApplicationScene(
_ templateApplicationScene: CPTemplateApplicationScene,
didConnect interfaceController: CPInterfaceController,
to window: CPWindow
) {
configure()
ferrostarManager!.templateApplicationScene(templateApplicationScene,
didConnect: interfaceController,
to: window)
}
}

extension CarPlaySceneDelegate: CPTemplateApplicationDashboardSceneDelegate {
func templateApplicationDashboardScene(_: CPTemplateApplicationDashboardScene,
didConnect _: CPDashboardController,
to _: UIWindow) {}

func templateApplicationDashboardScene(_: CPTemplateApplicationDashboardScene,
didDisconnect _: CPDashboardController,
from _: UIWindow) {}
}

extension CarPlaySceneDelegate: CPTemplateApplicationInstrumentClusterSceneDelegate {
// swiftlint:disable identifier_name vertical_parameter_alignment
func templateApplicationInstrumentClusterScene(
_: CPTemplateApplicationInstrumentClusterScene,
didConnect _: CPInstrumentClusterController
) {}

func templateApplicationInstrumentClusterScene(
_: CPTemplateApplicationInstrumentClusterScene,
didDisconnectInstrumentClusterController _: CPInstrumentClusterController
) {}
// swiftlint:enable identifier_name vertical_parameter_alignment
}
14 changes: 0 additions & 14 deletions apple/DemoApp/Demo/ConfigurationView.swift

This file was deleted.

9 changes: 9 additions & 0 deletions apple/DemoApp/Demo/DemoApp.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
import SwiftUI

// This AppDelegate setup is an easy way to share your environment with CarPlay
class DemoAppDelegate: NSObject, UIApplicationDelegate {
let appEnvironment = AppEnvironment()
}

@main
struct DemoApp: App {
@UIApplicationDelegateAdaptor(DemoAppDelegate.self) private var appDelegate: DemoAppDelegate

var body: some Scene {
WindowGroup {
DemoNavigationView()
.environmentObject(appDelegate.appEnvironment)
.environmentObject(appDelegate.appEnvironment.ferrostarCore)
}
}
}
Loading
Loading