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

Revamp waypoint types #388

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
* Swift is now required to directly use public types and methods defined by this library. If your application is written in Objective-C or Cocoa-AppleScript, you need to implement your own wrapper in Swift that bridges to Objective-C. ([#382](https://github.com/mapbox/mapbox-directions-swift/pull/382))
* This library now depends on [Turf](https://github.com/mapbox/turf-swift/). ([#382](https://github.com/mapbox/mapbox-directions-swift/pull/382))

### Locations and geometry

* Added `matchings_index` and `waypoint_index` to the `Tracepoint` structure to correctly reflect Map Matching response data.
* Replaced the `Waypoint` and `Tracepoint` classes with separate classes for requests and responses. Now you create a `RouteOptions` or `MatchOptions` using a series of `DirectionsOptions.Waypoint` instances (also known as `RouteOptions.Waypoint` or `MatchOptions.Waypoint`). The `RouteCompletionHandler` and `MatchCompletionHandler` closures and the response types represent waypoints as `DirectionsResult.Waypoint` (also known as `RouteOptions.Waypoint`) and tracepoints as `Match.Tracepoint`. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* Replaced the `Route.coordinates` property with `Route.shape` and the `RouteStep.coordinates` property with `RouteStep.shape`. The `Route.coordinateCount` and `RouteStep.coordinateCount` properties have been removed, but you can use the `LineString.coordinates` property to get the array of `CLLocationCoordinate2D`s. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* `RouteLeg.source` and `RouteLeg.destination` are now optional. They can be `nil` when the `RouteLeg` object is decoded individually from JSON. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))
* Renamed the `Tracepoint.alternateCount` property to `Tracepoint.countOfAlternatives`. ([#382](https://github.com/mapbox/MapboxDirections.swift/pull/382))

### Error handling

* The `RouteCompletionHandler` and `MatchCompletionHandler` closures’ `error` argument is now a `DirectionsError` instead of an `NSError`. ([#382](https://github.com/mapbox/mapbox-directions-swift/pull/382))
Expand Down
20 changes: 12 additions & 8 deletions MapboxDirections.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
2B2759BF243DE1F100D9F9AC /* RouteWaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B2759BE243DE1F100D9F9AC /* RouteWaypointTests.swift */; };
35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; };
35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; };
35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35828C9D217A003F00ED546E /* OfflineDirections.swift */; };
Expand Down Expand Up @@ -224,9 +225,9 @@
DA1A110D1D01045E009F82FA /* DirectionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA1A110A1D01045E009F82FA /* DirectionsTests.swift */; };
DA2E03E91CB0E0B000D1269A /* RouteStep.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2E03E81CB0E0B000D1269A /* RouteStep.swift */; };
DA2E03EB1CB0E13D00D1269A /* RouteOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA2E03EA1CB0E13D00D1269A /* RouteOptions.swift */; };
DA4F84ED21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA4F84EE21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA4F84EF21C08BFB008A0434 /* WaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */; };
DA4F84ED21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* RouteOptionsWaypointTests.swift */; };
DA4F84EE21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* RouteOptionsWaypointTests.swift */; };
DA4F84EF21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA4F84EC21C08BFB008A0434 /* RouteOptionsWaypointTests.swift */; };
DA688B3E21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
DA688B3F21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
DA688B4021B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */; };
Expand Down Expand Up @@ -350,6 +351,7 @@
/* End PBXCopyFilesBuildPhase section */

/* Begin PBXFileReference section */
2B2759BE243DE1F100D9F9AC /* RouteWaypointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteWaypointTests.swift; sourceTree = "<group>"; };
3556CE9922649CF2009397B5 /* MapboxDirectionsTests-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "MapboxDirectionsTests-Bridging-Header.h"; path = "../objc/MapboxDirectionsTests-Bridging-Header.h"; sourceTree = "<group>"; };
35828C9D217A003F00ED546E /* OfflineDirections.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OfflineDirections.swift; sourceTree = "<group>"; };
35CC310A2285739700EA1966 /* WalkingOptionsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalkingOptionsTests.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -413,7 +415,7 @@
DA1A110A1D01045E009F82FA /* DirectionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DirectionsTests.swift; sourceTree = "<group>"; };
DA2E03E81CB0E0B000D1269A /* RouteStep.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteStep.swift; sourceTree = "<group>"; };
DA2E03EA1CB0E13D00D1269A /* RouteOptions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteOptions.swift; sourceTree = "<group>"; };
DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WaypointTests.swift; sourceTree = "<group>"; };
DA4F84EC21C08BFB008A0434 /* RouteOptionsWaypointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteOptionsWaypointTests.swift; sourceTree = "<group>"; };
DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VisualInstructionComponentTests.swift; sourceTree = "<group>"; };
DA6C9D881CAE442B00094FBC /* MapboxDirections.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MapboxDirections.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DA6C9D8A1CAE442B00094FBC /* MapboxDirections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MapboxDirections.h; sourceTree = "<group>"; };
Expand Down Expand Up @@ -695,7 +697,8 @@
C52CE3921F6AF6E70069963D /* VisualInstructionTests.swift */,
DA688B3D21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift */,
35CC310A2285739700EA1966 /* WalkingOptionsTests.swift */,
DA4F84EC21C08BFB008A0434 /* WaypointTests.swift */,
DA4F84EC21C08BFB008A0434 /* RouteOptionsWaypointTests.swift */,
2B2759BE243DE1F100D9F9AC /* RouteWaypointTests.swift */,
912DF1A5240507640095D3D2 /* TransportTypeTests.swift */,
);
name = MapboxDirectionsTests;
Expand Down Expand Up @@ -1269,7 +1272,7 @@
DAE2DF6923AECB120065057A /* QuickLookTests.swift in Sources */,
DA1A110C1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C5D1D7F31F6AFBD600A1C4F1 /* VisualInstructionTests.swift in Sources */,
DA4F84EE21C08BFB008A0434 /* WaypointTests.swift in Sources */,
DA4F84EE21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */,
DAABF78F2395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */,
35CC310C2285739700EA1966 /* WalkingOptionsTests.swift in Sources */,
DABE6C7F236A37E200D370F4 /* JSONSerialization.swift in Sources */,
Expand Down Expand Up @@ -1348,7 +1351,7 @@
DAE2DF6A23AECB120065057A /* QuickLookTests.swift in Sources */,
DA1A110D1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C5D1D7F41F6AFBD600A1C4F1 /* VisualInstructionTests.swift in Sources */,
DA4F84EF21C08BFB008A0434 /* WaypointTests.swift in Sources */,
DA4F84EF21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */,
DAABF7902395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */,
35CC310D2285739700EA1966 /* WalkingOptionsTests.swift in Sources */,
DABE6C80236A37E200D370F4 /* JSONSerialization.swift in Sources */,
Expand Down Expand Up @@ -1474,12 +1477,13 @@
DAE2DF6823AECB120065057A /* QuickLookTests.swift in Sources */,
DA1A110B1D01045E009F82FA /* DirectionsTests.swift in Sources */,
C52CE3931F6AF6E70069963D /* VisualInstructionTests.swift in Sources */,
DA4F84ED21C08BFB008A0434 /* WaypointTests.swift in Sources */,
DA4F84ED21C08BFB008A0434 /* RouteOptionsWaypointTests.swift in Sources */,
DAABF78E2395ABA900CEEB61 /* SpokenInstructionTests.swift in Sources */,
35CC310B2285739700EA1966 /* WalkingOptionsTests.swift in Sources */,
DABE6C7E236A37E200D370F4 /* JSONSerialization.swift in Sources */,
DAABF7922395AE9800CEEB61 /* GeoJSONTests.swift in Sources */,
43538E3D23ED6A2000E010D4 /* DirectionsCredentialsTests.swift in Sources */,
2B2759BF243DE1F100D9F9AC /* RouteWaypointTests.swift in Sources */,
F4D785EF1DDD82C100FF4665 /* RouteStepTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ With the directions object in hand, construct a RouteOptions object and pass it
// main.swift

let waypoints = [
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox"),
Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House"),
RouteOptions.Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.9131752, longitude: -77.0324047), name: "Mapbox"),
RouteOptions.Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House"),
]
let options = RouteOptions(waypoints: waypoints, profileIdentifier: .automobileAvoidingTraffic)
options.includesSteps = true
Expand Down
2 changes: 1 addition & 1 deletion Sources/MapboxDirections/DirectionsResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ open class DirectionsResult: Codable {
} else {
shape = nil
}

routeIdentifier = try container.decodeIfPresent(String.self, forKey: .routeIdentifier)

if let identifier = try container.decodeIfPresent(String.self, forKey: .speechLocale) {
Expand Down
19 changes: 15 additions & 4 deletions Sources/MapboxDirections/MapMatching/MapMatchingResponse.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import Foundation

public struct MapMatchingResponse {

public let httpResponse: HTTPURLResponse?

public var matches : [Match]?
public var tracepoints: [Tracepoint?]?
public var tracepoints: [Match.Tracepoint?]?

public let options: MatchOptions
public let credentials: DirectionsCredentials
Expand All @@ -25,7 +26,7 @@ extension MapMatchingResponse: Codable {
case tracepoints
}

public init(httpResponse: HTTPURLResponse?, matches: [Match]? = nil, tracepoints: [Tracepoint]? = nil, options: MatchOptions, credentials: DirectionsCredentials) {
public init(httpResponse: HTTPURLResponse?, matches: [Match]? = nil, tracepoints: [Match.Tracepoint]? = nil, options: MatchOptions, credentials: DirectionsCredentials) {
self.httpResponse = httpResponse
self.matches = matches
self.tracepoints = tracepoints
Expand All @@ -41,15 +42,25 @@ extension MapMatchingResponse: Codable {
guard let options = decoder.userInfo[.options] as? MatchOptions else {
throw DirectionsCodingError.missingOptions
}

self.options = options

guard let credentials = decoder.userInfo[.credentials] as? DirectionsCredentials else {
throw DirectionsCodingError.missingCredentials
}
self.credentials = credentials

tracepoints = try container.decodeIfPresent([Tracepoint?].self, forKey: .tracepoints)
tracepoints = try container.decodeIfPresent([Match.Tracepoint?].self, forKey: .tracepoints)
matches = try container.decodeIfPresent([Match].self, forKey: .matches)


if let sortedTracepoints = self.tracepoints?.sorted(by: {
($0?.waypointIndex ?? -1) < ($1?.waypointIndex ?? -1)
}) {
matches?.enumerated().forEach { (index, element) in
element.tracepoints = sortedTracepoints.filter {
$0?.matchingIndex == index
}
}
}
}
}
17 changes: 14 additions & 3 deletions Sources/MapboxDirections/MapMatching/Match.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public enum Weight: Equatable {
open class Match: DirectionsResult {
private enum CodingKeys: String, CodingKey {
case confidence
case tracepoints
case weight
case weightName = "weight_name"
}
Expand All @@ -61,11 +62,12 @@ open class Match: DirectionsResult {
- parameter distance: The matched path’s cumulative distance, measured in meters.
- parameter expectedTravelTime: The route’s expected travel time, measured in seconds.
- parameter confidence: A number between 0 and 1 that indicates the Map Matching API’s confidence that the match is accurate. A higher confidence means the match is more likely to be accurate.
- parameter tracepoints: Tracepoints on the road network that match the tracepoints in `options`.
- parameter options: The criteria to match.
- parameter tracepoints: Tracepoints on the road network that match the tracepoints in options.
- parameter weight: A `Weight` used to determine units and weight type used for matching
*/
public init(legs: [RouteLeg], shape: LineString?, distance: CLLocationDistance, expectedTravelTime: TimeInterval, confidence: Float, weight: Weight) {
public init(legs: [RouteLeg], shape: LineString?, distance: CLLocationDistance, expectedTravelTime: TimeInterval, confidence: Float, tracepoints: [Tracepoint?], weight: Weight) {
self.confidence = confidence
self.tracepoints = tracepoints
self.weight = weight
super.init(legs: legs, shape: shape, distance: distance, expectedTravelTime: expectedTravelTime)
}
Expand All @@ -79,6 +81,7 @@ open class Match: DirectionsResult {
public required init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
confidence = try container.decode(Float.self, forKey: .confidence)
tracepoints = try container.decodeIfPresent([Tracepoint?].self, forKey: .tracepoints) ?? []
let weightValue = try container.decode(Float.self, forKey: .weight)
let weightMetric = try container.decode(String.self, forKey: .weightName)

Expand All @@ -90,6 +93,7 @@ open class Match: DirectionsResult {
public override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(confidence, forKey: .confidence)
try container.encode(tracepoints, forKey: .tracepoints)
try container.encode(weight.value, forKey: .weight)
try container.encode(weight.metric, forKey: .weightName)

Expand All @@ -103,6 +107,12 @@ open class Match: DirectionsResult {
*/
open var confidence: Float

/**
Tracepoints on the road network that match the tracepoints in the match options.

Any outlier tracepoint is omitted from the match. This array represents an outlier tracepoint if the element is `nil`.
*/
open var tracepoints: [Tracepoint?]
}

extension Match: Equatable {
Expand All @@ -112,6 +122,7 @@ extension Match: Equatable {
lhs.expectedTravelTime == rhs.expectedTravelTime &&
lhs.speechLocale == rhs.speechLocale &&
lhs.confidence == rhs.confidence &&
lhs.tracepoints == rhs.tracepoints &&
lhs.weight == rhs.weight &&
lhs.legs == rhs.legs &&
lhs.shape == rhs.shape
Expand Down
74 changes: 46 additions & 28 deletions Sources/MapboxDirections/MapMatching/Tracepoint.swift
Original file line number Diff line number Diff line change
@@ -1,40 +1,58 @@
import Foundation
import CoreLocation

/**
A `Tracepoint` represents a location matched to the road network.
*/
public class Tracepoint: Waypoint {
public extension Match {
/**
Number of probable alternative matchings for this tracepoint. A value of zero indicates that this point was matched unambiguously.
A tracepoint represents a location matched to the road network.
*/
public let countOfAlternatives: Int

struct Tracepoint: Matchpoint, Equatable {
/**
The geographic coordinate of the waypoint, snapped to the road network.
*/
public var coordinate: CLLocationCoordinate2D

/**
The straight-line distance from this waypoint to the corresponding waypoint in the `RouteOptions` or `MatchOptions` object.

The requested waypoint is snapped to the road network. This property contains the straight-line distance from the original requested waypoint’s `DirectionsOptions.Waypoint.coordinate` property to the `coordinate` property.
*/
public var correction: CLLocationDistance

/**
Number of probable alternative matchings for this tracepoint. A value of zero indicates that this point was matched unambiguously.
*/
public var countOfAlternatives: Int

/**
The name of the road or path the coordinate snapped to.
*/
public var name: String?

/***
The index of the match object in matchings that the sub-trace was matched to.
*/
public var matchingIndex: Int

/***
The index of the waypoint inside the matched route.
*/
public var waypointIndex: Int
}
}

extension Match.Tracepoint: Codable {
private enum CodingKeys: String, CodingKey {
case coordinate = "location"
case correction = "distance"
case countOfAlternatives = "alternatives_count"
}

init(coordinate: CLLocationCoordinate2D, countOfAlternatives: Int, name: String?) {
self.countOfAlternatives = countOfAlternatives
super.init(coordinate: coordinate, name: name)
}

required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
countOfAlternatives = try container.decode(Int.self, forKey: .countOfAlternatives)
try super.init(from: decoder)
}

public override func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(countOfAlternatives, forKey: .countOfAlternatives)
try super.encode(to: encoder)
case name
case matchingIndex = "matchings_index"
case waypointIndex = "waypoint_index"
}
}

extension Tracepoint { //Equatable
public static func ==(lhs: Tracepoint, rhs: Tracepoint) -> Bool {
let superEquals = (lhs as Waypoint == rhs as Waypoint)
return superEquals && lhs.countOfAlternatives == rhs.countOfAlternatives
extension Match.Tracepoint: CustomStringConvertible {
public var description: String {
return "<latitude: \(coordinate.latitude); longitude: \(coordinate.longitude)>"
}
}
Loading