Skip to content

Commit

Permalink
Revisions for demo app simulation
Browse files Browse the repository at this point in the history
  • Loading branch information
Archdoog committed Jan 15, 2024
1 parent fa30bef commit dee2854
Show file tree
Hide file tree
Showing 20 changed files with 89 additions and 21 deletions.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,3 @@ struct LocationIdentifier : Identifiable, Equatable, Hashable {
let locations = [
LocationIdentifier(name: "Cupertino HS", coordinate: CLLocationCoordinate2D(latitude: 37.31910, longitude: -122.01018)),
]


Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@ let style = URL(string: "https://tiles.stadiamaps.com/styles/outdoors.json?api_k

struct NavigationView: View {

private var locationManager: LiveLocationProvider
private let initialLocation = CLLocation(latitude: 37.332726,
longitude: -122.031790)

private var locationManager: LocationProviding
@ObservedObject private var ferrostarCore: FerrostarCore

@State private var isFetchingRoutes = false
Expand All @@ -29,7 +32,7 @@ struct NavigationView: View {
}

init() {
locationManager = LiveLocationProvider(activityType: .otherNavigation)
locationManager = SimulatedLocationProvider(location: initialLocation)
_ferrostarCore = ObservedObject(
wrappedValue: FerrostarCore(
valhallaEndpointUrl: URL(string: "https://api.stadiamaps.com/route/v1?api_key=\(APIKeys.shared.stadiaMapsAPIKey)")!,
Expand All @@ -48,7 +51,7 @@ struct NavigationView: View {
lightStyleURL: style,
darkStyleURL: style,
navigationState: ferrostarCore.observableState,
initialCamera: .center(locations.first!.coordinate, zoom: 14),
initialCamera: .center(initialLocation.coordinate, zoom: 14),
previewRoutes: routes
)
.overlay(alignment: .bottomLeading) {
Expand Down Expand Up @@ -171,6 +174,11 @@ struct NavigationView: View {
try ferrostarCore.startNavigation(
route: route,
stepAdvance: .relativeLineStringDistance(minimumHorizontalAccuracy: 32, automaticAdvanceDistance: 10))

if let simulated = locationManager as? SimulatedLocationProvider {
try simulated.start(route: route)
print("DemoApp: starting route simulation")
}
}

var locationLabel: String {
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
1611A55D2B2E6E98006B131D /* DemoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1611A5582B2E6E98006B131D /* DemoApp.swift */; };
1663679D2B2F6F85008BFF1F /* APIKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1663679C2B2F6F85008BFF1F /* APIKeys.swift */; };
1663679F2B2F8FB3008BFF1F /* MockLocationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1663679E2B2F8FB3008BFF1F /* MockLocationData.swift */; };
167FD69B2B54B55A00CB4445 /* FerrostarCore in Frameworks */ = {isa = PBXBuildFile; productRef = 167FD69A2B54B55A00CB4445 /* FerrostarCore */; };
167FD69D2B54B55A00CB4445 /* FerrostarMapLibreUI in Frameworks */ = {isa = PBXBuildFile; productRef = 167FD69C2B54B55A00CB4445 /* FerrostarMapLibreUI */; };
168ECA7A2B2E8C42007B11DE /* API-Keys.plist in Resources */ = {isa = PBXBuildFile; fileRef = 168ECA792B2E8C42007B11DE /* API-Keys.plist */; };
169A1D562B2E8280006CE59E /* FerrostarCore in Frameworks */ = {isa = PBXBuildFile; productRef = 169A1D552B2E8280006CE59E /* FerrostarCore */; };
169A1D582B2E8280006CE59E /* FerrostarMapLibreUI in Frameworks */ = {isa = PBXBuildFile; productRef = 169A1D572B2E8280006CE59E /* FerrostarMapLibreUI */; };
Expand Down Expand Up @@ -44,8 +46,10 @@
files = (
E9505FCA2AD449B30016BF0A /* FerrostarCore in Frameworks */,
169B50152B2E46800008EBB7 /* FerrostarMapLibreUI in Frameworks */,
167FD69B2B54B55A00CB4445 /* FerrostarCore in Frameworks */,
E9505FCC2AD449B30016BF0A /* FerrostarMapLibreUI in Frameworks */,
169A1D582B2E8280006CE59E /* FerrostarMapLibreUI in Frameworks */,
167FD69D2B54B55A00CB4445 /* FerrostarMapLibreUI in Frameworks */,
169B50132B2E46800008EBB7 /* FerrostarCore in Frameworks */,
169A1D562B2E8280006CE59E /* FerrostarCore in Frameworks */,
);
Expand Down Expand Up @@ -127,6 +131,8 @@
169B50142B2E46800008EBB7 /* FerrostarMapLibreUI */,
169A1D552B2E8280006CE59E /* FerrostarCore */,
169A1D572B2E8280006CE59E /* FerrostarMapLibreUI */,
167FD69A2B54B55A00CB4445 /* FerrostarCore */,
167FD69C2B54B55A00CB4445 /* FerrostarMapLibreUI */,
);
productName = "iOS Demo";
productReference = E9505FB72AD449700016BF0A /* Ferrostar Demo.app */;
Expand Down Expand Up @@ -157,7 +163,7 @@
);
mainGroup = E9505FAE2AD449700016BF0A;
packageReferences = (
169A1D542B2E8280006CE59E /* XCLocalSwiftPackageReference "../.." */,
167FD6992B54B55A00CB4445 /* XCLocalSwiftPackageReference "../.." */,
);
productRefGroup = E9505FB82AD449700016BF0A /* Products */;
projectDirPath = "";
Expand Down Expand Up @@ -403,13 +409,21 @@
/* End XCConfigurationList section */

/* Begin XCLocalSwiftPackageReference section */
169A1D542B2E8280006CE59E /* XCLocalSwiftPackageReference "../.." */ = {
167FD6992B54B55A00CB4445 /* XCLocalSwiftPackageReference "../.." */ = {
isa = XCLocalSwiftPackageReference;
relativePath = ../..;
};
/* End XCLocalSwiftPackageReference section */

/* Begin XCSwiftPackageProductDependency section */
167FD69A2B54B55A00CB4445 /* FerrostarCore */ = {
isa = XCSwiftPackageProductDependency;
productName = FerrostarCore;
};
167FD69C2B54B55A00CB4445 /* FerrostarMapLibreUI */ = {
isa = XCSwiftPackageProductDependency;
productName = FerrostarMapLibreUI;
};
169A1D552B2E8280006CE59E /* FerrostarCore */ = {
isa = XCSwiftPackageProductDependency;
productName = FerrostarCore;
Expand Down
File renamed without changes.
File renamed without changes.
8 changes: 6 additions & 2 deletions apple/Sources/FerrostarCore/FerrostarCore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ public protocol FerrostarCoreDelegate: AnyObject {
observableState = FerrostarObservableState(snappedLocation: location, heading: locationProvider.lastHeading, fullRoute: route.geometry, steps: route.inner.steps)
let controller = NavigationController(route: route.inner, config: NavigationControllerConfig(stepAdvance: stepAdvance.ffiValue))
navigationController = controller
update(newState: controller.getInitialState(location: location.userLocation), location: location)
DispatchQueue.main.async {
self.update(newState: controller.getInitialState(location: location.userLocation), location: location)
}
}

/// Stops navigation and stops requesting location updates (to save battery).
Expand Down Expand Up @@ -177,7 +179,9 @@ extension FerrostarCore: LocationManagingDelegate {
return
}

update(newState: newState, location: location)
DispatchQueue.main.async {
self.update(newState: newState, location: location)
}
}

public func locationManager(_ manager: LocationProviding, didUpdateHeading newHeading: CLHeading) {
Expand Down
50 changes: 47 additions & 3 deletions apple/Sources/FerrostarCore/Location.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import CoreLocation
import UniFFI

public protocol LocationProviding: AnyObject {
var delegate: LocationManagingDelegate? { get set }
var authorizationStatus: CLAuthorizationStatus { get }
var lastLocation: CLLocation? { get }
var lastHeading: CLHeading? { get }

Expand Down Expand Up @@ -87,11 +89,13 @@ extension LiveLocationProvider: CLLocationManagerDelegate {
}
}

/// Location provider for testing without relying on simulator location spoofing.
///
/// This allows for more granular unit tests.
public class SimulatedLocationProvider: LocationProviding, ObservableObject {

public var delegate: LocationManagingDelegate?
public private(set) var authorizationStatus: CLAuthorizationStatus = .authorizedAlways

public private(set) var simulationState: LocationSimulationState?

@Published public var lastLocation: CLLocation? {
didSet {
notifyDelegateOfLocation()
Expand All @@ -110,9 +114,27 @@ public class SimulatedLocationProvider: LocationProviding, ObservableObject {
notifyDelegateOfHeading()
}
}

public init() {

}

public init(coordinate: CLLocationCoordinate2D) {
lastLocation = CLLocation(latitude: coordinate.latitude, longitude: coordinate.longitude)
}

public init(location: CLLocation) {
lastLocation = location
}

public func start(route: Route) throws {
simulationState = try locationSimulationFromRoute(route: route.inner)
startUpdating()
}

public func startUpdating() {
isUpdating = true
updateLocation()
}

public func stopUpdating() {
Expand All @@ -130,4 +152,26 @@ public class SimulatedLocationProvider: LocationProviding, ObservableObject {
delegate?.locationManager(self, didUpdateHeading: heading)
}
}

private func updateLocation() {
Task {
guard isUpdating, let lastState = self.simulationState else {
return
}

try await Task.sleep(nanoseconds: 50 * NSEC_PER_MSEC)
let newState = advanceLocationSimulation(state: lastState, speed: .jumpToNextLocation)

if simulationState?.currentLocation == newState.currentLocation {
stopUpdating()
return
}

lastLocation = CLLocation(latitude: newState.currentLocation.lat, longitude: newState.currentLocation.lng)
simulationState = newState

updateLocation()
return
}
}
}
18 changes: 9 additions & 9 deletions apple/Sources/FerrostarCore/ObservableState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import UniFFI
///
/// While the core generally does not include UI, this is purely at the model layer and should be implemented
/// the same for all frontends.
public final class FerrostarObservableState: ObservableObject {
@Published public internal(set) var snappedLocation: CLLocation
@Published public internal(set) var heading: CLHeading?
@Published public internal(set) var courseOverGround: CLLocationDirection?
@Published public internal(set) var fullRouteShape: [CLLocationCoordinate2D]
@Published public internal(set) var currentStep: UniFFI.RouteStep?
@Published public internal(set) var visualInstructions: UniFFI.VisualInstruction?
@Published public internal(set) var spokenInstruction: UniFFI.SpokenInstruction?
@Published public internal(set) var distanceToNextManeuver: CLLocationDistance?
public struct FerrostarObservableState {
public internal(set) var snappedLocation: CLLocation
public internal(set) var heading: CLHeading?
public internal(set) var courseOverGround: CLLocationDirection?
public internal(set) var fullRouteShape: [CLLocationCoordinate2D]
public internal(set) var currentStep: UniFFI.RouteStep?
public internal(set) var visualInstructions: UniFFI.VisualInstruction?
public internal(set) var spokenInstruction: UniFFI.SpokenInstruction?
public internal(set) var distanceToNextManeuver: CLLocationDistance?

init(snappedLocation: CLLocation, heading: CLHeading? = nil, fullRoute: [CLLocationCoordinate2D], steps: [RouteStep]) {
self.snappedLocation = snappedLocation
Expand Down

0 comments on commit dee2854

Please sign in to comment.