diff --git a/CHANGELOG.md b/CHANGELOG.md index fcb5b81b96e..f3df909c0c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ * The `Intersection.approachIndex` and `Intersection.outletIndex` properties are now optional, not −1, in the case of a departure or arrival maneuver. ([#393](https://github.com/mapbox/MapboxDirections.swift/pull/393)) * Added initializers for `Route`, `Match`, `RouteLeg`, and `RouteStep`. ([#393](https://github.com/mapbox/MapboxDirections.swift/pull/393)) * Various properties of `Route`, `RouteLeg`, and `RouteStep` are now writable. ([#393](https://github.com/mapbox/MapboxDirections.swift/pull/393)) +* Added `AttributeOptions.maximumSpeedLimit` for getting maximum posted speed limits in the `RouteLeg.segmentMaximumSpeedLimits` property. ([#367](https://github.com/mapbox/MapboxDirections.swift/pull/367)) +* Added the `RouteLeg.segmentRangesByStep` property for more easily associating `RouteStep`s with the values in segment-based arrays such as `RouteLeg.segmentCongestionLevels`. ([#367](https://github.com/mapbox/MapboxDirections.swift/pull/367)) ## v0.30.0 diff --git a/Directions Example/ViewController.swift b/Directions Example/ViewController.swift index 5b02c68962a..15ac92a7a37 100644 --- a/Directions Example/ViewController.swift +++ b/Directions Example/ViewController.swift @@ -67,6 +67,8 @@ class ViewController: UIViewController, MBDrawingViewDelegate { let wp2 = Waypoint(coordinate: CLLocationCoordinate2D(latitude: 38.8977, longitude: -77.0365), name: "White House") let options = RouteOptions(waypoints: [wp1, wp2]) options.includesSteps = true + options.routeShapeResolution = .full + options.attributeOptions = [.congestionLevel, .maximumSpeedLimit] Directions.shared.calculate(options) { (waypoints, routes, error) in if let error = error { diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj index 00b04533223..5812ed16087 100644 --- a/MapboxDirections.xcodeproj/project.pbxproj +++ b/MapboxDirections.xcodeproj/project.pbxproj @@ -217,6 +217,9 @@ DA737EE41D05F91E005BDA16 /* v5_driving_dc_geojson.json in Resources */ = {isa = PBXBuildFile; fileRef = DA737EE31D05F91E005BDA16 /* v5_driving_dc_geojson.json */; }; DA737EE51D05F91E005BDA16 /* v5_driving_dc_geojson.json in Resources */ = {isa = PBXBuildFile; fileRef = DA737EE31D05F91E005BDA16 /* v5_driving_dc_geojson.json */; }; DA737EE61D05F91E005BDA16 /* v5_driving_dc_geojson.json in Resources */ = {isa = PBXBuildFile; fileRef = DA737EE31D05F91E005BDA16 /* v5_driving_dc_geojson.json */; }; + DA8F3A7223B56D3B00B56786 /* RouteLegTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA8F3A7123B56D3B00B56786 /* RouteLegTests.swift */; }; + DA8F3A7323B56D3B00B56786 /* RouteLegTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA8F3A7123B56D3B00B56786 /* RouteLegTests.swift */; }; + DA8F3A7423B56D3B00B56786 /* RouteLegTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA8F3A7123B56D3B00B56786 /* RouteLegTests.swift */; }; DA9E1B131E5A675F0081EDC7 /* Polyline.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DA9E1B121E5A675F0081EDC7 /* Polyline.framework */; }; DAA76D681DD127CB0015EC78 /* LaneIndication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA76D671DD127CB0015EC78 /* LaneIndication.swift */; }; DAA76D691DD127CB0015EC78 /* LaneIndication.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAA76D671DD127CB0015EC78 /* LaneIndication.swift */; }; @@ -260,6 +263,10 @@ DAE33A1B1F215DF600C06039 /* IntersectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */; }; DAE33A1C1F215DF600C06039 /* IntersectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */; }; DAE33A1D1F215DF600C06039 /* IntersectionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */; }; + DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE7EA93230B5FD10003B211 /* Measurement.swift */; }; + DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE7EA93230B5FD10003B211 /* Measurement.swift */; }; + DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE7EA93230B5FD10003B211 /* Measurement.swift */; }; + DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE7EA93230B5FD10003B211 /* Measurement.swift */; }; DAE9E0F41EB7DE2E001E8E8B /* RouteOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE9E0F31EB7DE2E001E8E8B /* RouteOptionsTests.swift */; }; DAE9E0F51EB7DE2E001E8E8B /* RouteOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE9E0F31EB7DE2E001E8E8B /* RouteOptionsTests.swift */; }; DAE9E0F61EB7DE2E001E8E8B /* RouteOptionsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = DAE9E0F31EB7DE2E001E8E8B /* RouteOptionsTests.swift */; }; @@ -271,6 +278,7 @@ F448F8401DDCC709000BC343 /* Polyline.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F448F83C1DDCC6F3000BC343 /* Polyline.framework */; }; F448F8411DDCC70C000BC343 /* OHHTTPStubs.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F448F83B1DDCC6F3000BC343 /* OHHTTPStubs.framework */; }; F448F8421DDCC70D000BC343 /* Polyline.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F448F83C1DDCC6F3000BC343 /* Polyline.framework */; }; + F4B0022A22650A9F00CF44FF /* MapboxDirections.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = DA6C9D881CAE442B00094FBC /* MapboxDirections.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; F4D785EF1DDD82C100FF4665 /* RouteStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D785EE1DDD82C100FF4665 /* RouteStepTests.swift */; }; F4D785F01DDD82C100FF4665 /* RouteStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D785EE1DDD82C100FF4665 /* RouteStepTests.swift */; }; F4D785F11DDD82C100FF4665 /* RouteStepTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = F4D785EE1DDD82C100FF4665 /* RouteStepTests.swift */; }; @@ -314,6 +322,7 @@ dstPath = ""; dstSubfolderSpec = 10; files = ( + F4B0022A22650A9F00CF44FF /* MapboxDirections.framework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -389,6 +398,7 @@ DA6C9DAB1CAEC72800094FBC /* V5Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = V5Tests.swift; sourceTree = ""; }; DA6C9DB11CAECA0E00094FBC /* Fixture.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Fixture.swift; sourceTree = ""; }; DA737EE31D05F91E005BDA16 /* v5_driving_dc_geojson.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = v5_driving_dc_geojson.json; sourceTree = ""; }; + DA8F3A7123B56D3B00B56786 /* RouteLegTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteLegTests.swift; sourceTree = ""; }; DA9E1B121E5A675F0081EDC7 /* Polyline.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Polyline.framework; path = Carthage/Build/watchOS/Polyline.framework; sourceTree = ""; }; DAA76D671DD127CB0015EC78 /* LaneIndication.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaneIndication.swift; sourceTree = ""; }; DAABF78D2395ABA900CEEB61 /* SpokenInstructionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpokenInstructionTests.swift; sourceTree = ""; }; @@ -411,6 +421,7 @@ DAE2DF6723AECB120065057A /* QuickLookTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookTests.swift; sourceTree = ""; }; DAE2DF6B23AED2280065057A /* RouteTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; }; DAE33A1A1F215DF600C06039 /* IntersectionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = IntersectionTests.swift; sourceTree = ""; }; + DAE7EA93230B5FD10003B211 /* Measurement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Measurement.swift; sourceTree = ""; }; DAE9E0F31EB7DE2E001E8E8B /* RouteOptionsTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RouteOptionsTests.swift; path = Tests/MapboxDirectionsTests/RouteOptionsTests.swift; sourceTree = SOURCE_ROOT; }; DD6254731AE70CB700017857 /* Directions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Directions.swift; sourceTree = ""; }; F448F82A1DDCC5B6000BC343 /* Polyline.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Polyline.framework; path = Carthage/Build/iOS/Polyline.framework; sourceTree = ""; }; @@ -540,6 +551,7 @@ 8D434678219E1167008B7BF3 /* Double.swift */, 43208BAA2343F81900D8BD89 /* GeoJSON.swift */, 35DBF00E217E17A30009D2AE /* HTTPURLResponse.swift */, + DAE7EA93230B5FD10003B211 /* Measurement.swift */, 8D381B691FDB101F008D5A58 /* String.swift */, ); path = Extensions; @@ -646,6 +658,7 @@ DAE2DF6723AECB120065057A /* QuickLookTests.swift */, C59666382048A20E00C45CE5 /* RoutableMatchTests.swift */, DAE2DF6B23AED2280065057A /* RouteTests.swift */, + DA8F3A7123B56D3B00B56786 /* RouteLegTests.swift */, DAE9E0F31EB7DE2E001E8E8B /* RouteOptionsTests.swift */, F4D785EE1DDD82C100FF4665 /* RouteStepTests.swift */, DAABF78D2395ABA900CEEB61 /* SpokenInstructionTests.swift */, @@ -1170,6 +1183,7 @@ DA1A10C91D00F969009F82FA /* RouteLeg.swift in Sources */, C52552BA1FA15D5E00B1545C /* VisualInstructionBanner.swift in Sources */, 35828C9F217A003F00ED546E /* OfflineDirections.swift in Sources */, + DAE7EA95230B5FD10003B211 /* Measurement.swift in Sources */, C5DAAC9F20195AAE001F9261 /* Tracepoint.swift in Sources */, 431E93CC23466C2500A71B44 /* RouteResponse.swift in Sources */, 431E93C423466B0F00A71B44 /* GeoJSON.swift in Sources */, @@ -1212,6 +1226,7 @@ DA688B3F21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */, DAD06E36239F0B19001A917D /* DirectionsErrorTests.swift in Sources */, C596663A2048AECD00C45CE5 /* RoutableMatchTests.swift in Sources */, + DA8F3A7323B56D3B00B56786 /* RouteLegTests.swift in Sources */, DA1A10CD1D00F972009F82FA /* V5Tests.swift in Sources */, DAE33A1C1F215DF600C06039 /* IntersectionTests.swift in Sources */, C5DAACB0201AA92B001F9261 /* MatchTests.swift in Sources */, @@ -1241,6 +1256,7 @@ DA1A10EF1D010247009F82FA /* RouteLeg.swift in Sources */, C52552BB1FA15D5F00B1545C /* VisualInstructionBanner.swift in Sources */, 35828CA0217A003F00ED546E /* OfflineDirections.swift in Sources */, + DAE7EA96230B5FD10003B211 /* Measurement.swift in Sources */, C5DAACA020195AAF001F9261 /* Tracepoint.swift in Sources */, 431E93CD23466C2700A71B44 /* RouteResponse.swift in Sources */, 431E93C523466B1000A71B44 /* GeoJSON.swift in Sources */, @@ -1283,6 +1299,7 @@ DA688B4021B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */, DAD06E37239F0B19001A917D /* DirectionsErrorTests.swift in Sources */, C596663B2048AECE00C45CE5 /* RoutableMatchTests.swift in Sources */, + DA8F3A7423B56D3B00B56786 /* RouteLegTests.swift in Sources */, DA1A10F41D010251009F82FA /* V5Tests.swift in Sources */, DAE33A1D1F215DF600C06039 /* IntersectionTests.swift in Sources */, C5DAACB1201AA92B001F9261 /* MatchTests.swift in Sources */, @@ -1312,6 +1329,7 @@ DA1A11061D0103A3009F82FA /* RouteLeg.swift in Sources */, C52552BC1FA15D6000B1545C /* VisualInstructionBanner.swift in Sources */, 35828CA1217A003F00ED546E /* OfflineDirections.swift in Sources */, + DAE7EA97230B5FD10003B211 /* Measurement.swift in Sources */, C5DAACA120195AAF001F9261 /* Tracepoint.swift in Sources */, 431E93CE23466C2800A71B44 /* RouteResponse.swift in Sources */, 431E93C623466B1100A71B44 /* GeoJSON.swift in Sources */, @@ -1356,6 +1374,7 @@ 43F89F98235778DE007B591E /* MapMatchingResponse.swift in Sources */, C52552B91FA15D5900B1545C /* VisualInstructionBanner.swift in Sources */, 35828C9E217A003F00ED546E /* OfflineDirections.swift in Sources */, + DAE7EA94230B5FD10003B211 /* Measurement.swift in Sources */, C59426071F1EA6C400C8E59C /* RoadClasses.swift in Sources */, 431E93CB23466C2400A71B44 /* RouteResponse.swift in Sources */, 431E93C323466B0E00A71B44 /* GeoJSON.swift in Sources */, @@ -1398,6 +1417,7 @@ DA688B3E21B89ECD00C9BB25 /* VisualInstructionComponentTests.swift in Sources */, DAD06E35239F0B19001A917D /* DirectionsErrorTests.swift in Sources */, C59666392048A20E00C45CE5 /* RoutableMatchTests.swift in Sources */, + DA8F3A7223B56D3B00B56786 /* RouteLegTests.swift in Sources */, DA6C9DAC1CAEC72800094FBC /* V5Tests.swift in Sources */, DAE33A1B1F215DF600C06039 /* IntersectionTests.swift in Sources */, C5DAACAF201AA92B001F9261 /* MatchTests.swift in Sources */, diff --git a/Package.swift b/Package.swift index f8e35ac8095..783de15e059 100644 --- a/Package.swift +++ b/Package.swift @@ -5,6 +5,9 @@ import PackageDescription let package = Package( name: "MapboxDirections", + platforms: [ + .macOS(.v10_12), .iOS(.v10), .watchOS(.v4), .tvOS(.v12) + ], products: [ // Products define the executables and libraries produced by a package, and make them visible to other packages. .library( diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index 1504e663f4f..a0deed89dc9 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -42,6 +42,13 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible { */ public static let congestionLevel = AttributeOptions(rawValue: 1 << 4) + /** + The maximum speed limit along the segment. + + When this attribute is specified, the `RouteLeg.segmentMaximumSpeedLimits` property contains one value for each segment in the leg’s full geometry. + */ + public static let maximumSpeedLimit = AttributeOptions(rawValue: 1 << 5) + /** Creates an AttributeOptions from the given description strings. */ @@ -57,6 +64,8 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible { attributeOptions.update(with: .speed) case "congestion": attributeOptions.update(with: .congestionLevel) + case "maxspeed": + attributeOptions.update(with: .maximumSpeedLimit) case "": continue default: @@ -80,6 +89,9 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible { if contains(.congestionLevel) { descriptions.append("congestion") } + if contains(.maximumSpeedLimit) { + descriptions.append("maxspeed") + } return descriptions.joined(separator: ",") } } diff --git a/Sources/MapboxDirections/Extensions/Measurement.swift b/Sources/MapboxDirections/Extensions/Measurement.swift new file mode 100644 index 00000000000..21111a56aa9 --- /dev/null +++ b/Sources/MapboxDirections/Extensions/Measurement.swift @@ -0,0 +1,100 @@ +import Foundation + +enum SpeedLimitDescriptor: Equatable { + enum UnitDescriptor: String, Codable { + case milesPerHour = "mph" + case kilometersPerHour = "km/h" + + init?(unit: UnitSpeed) { + switch unit { + case .milesPerHour: + self = .milesPerHour + case .kilometersPerHour: + self = .kilometersPerHour + default: + return nil + } + } + + var describedUnit: UnitSpeed { + switch self { + case .milesPerHour: + return .milesPerHour + case .kilometersPerHour: + return .kilometersPerHour + } + } + } + + enum CodingKeys: String, CodingKey { + case none + case speed + case unknown + case unit + } + + case none + case some(speed: Measurement) + case unknown + + init(speed: Measurement?) { + guard let speed = speed else { + self = .unknown + return + } + + if speed.value.isInfinite { + self = .none + } else { + self = .some(speed: speed) + } + } +} + +extension SpeedLimitDescriptor: Codable { + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + if (try container.decodeIfPresent(Bool.self, forKey: .none)) ?? false { + self = .none + } else if (try container.decodeIfPresent(Bool.self, forKey: .unknown)) ?? false { + self = .unknown + } else { + let unitDescriptor = try container.decode(UnitDescriptor.self, forKey: .unit) + let unit = unitDescriptor.describedUnit + let value = try container.decode(Double.self, forKey: .speed) + self = .some(speed: .init(value: value, unit: unit)) + } + } + + func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .none: + try container.encode(true, forKey: .none) + case .some(var speed): + let unitDescriptor = UnitDescriptor(unit: speed.unit) ?? { + speed = speed.converted(to: .kilometersPerHour) + return .kilometersPerHour + }() + try container.encode(unitDescriptor, forKey: .unit) + try container.encode(speed.value, forKey: .speed) + case .unknown: + try container.encode(true, forKey: .unknown) + } + } +} + +extension Measurement where UnitType == UnitSpeed { + init?(speedLimitDescriptor: SpeedLimitDescriptor) { + switch speedLimitDescriptor { + case .none: + self = .init(value: .infinity, unit: .kilometersPerHour) + case .some(let speed): + self = speed + case .unknown: + return nil + } + } +} diff --git a/Sources/MapboxDirections/RouteLeg.swift b/Sources/MapboxDirections/RouteLeg.swift index b2169de5478..d86e1801043 100644 --- a/Sources/MapboxDirections/RouteLeg.swift +++ b/Sources/MapboxDirections/RouteLeg.swift @@ -25,6 +25,7 @@ open class RouteLeg: Codable { case expectedSegmentTravelTimes = "duration" case segmentSpeeds = "speed" case segmentCongestionLevels = "congestion" + case segmentMaximumSpeedLimits = "maxspeed" } // MARK: Creating a Leg @@ -78,6 +79,12 @@ open class RouteLeg: Codable { expectedSegmentTravelTimes = try annotation?.decodeIfPresent([TimeInterval].self, forKey: .expectedSegmentTravelTimes) segmentSpeeds = try annotation?.decodeIfPresent([CLLocationSpeed].self, forKey: .segmentSpeeds) segmentCongestionLevels = try annotation?.decodeIfPresent([CongestionLevel].self, forKey: .segmentCongestionLevels) + + if let speedLimitDescriptors = try annotation?.decodeIfPresent([SpeedLimitDescriptor].self, forKey: .segmentMaximumSpeedLimits) { + segmentMaximumSpeedLimits = speedLimitDescriptors.map { Measurement(speedLimitDescriptor: $0) } + } else { + segmentMaximumSpeedLimits = nil + } } public func encode(to encoder: Encoder) throws { @@ -90,12 +97,16 @@ open class RouteLeg: Codable { try container.encode(expectedTravelTime, forKey: .expectedTravelTime) try container.encode(profileIdentifier, forKey: .profileIdentifier) - if segmentDistances != nil || expectedSegmentTravelTimes != nil || segmentSpeeds != nil || segmentCongestionLevels != nil { + if segmentDistances != nil || expectedSegmentTravelTimes != nil || segmentSpeeds != nil || segmentCongestionLevels != nil || segmentMaximumSpeedLimits != nil { var annotationContainer = container.nestedContainer(keyedBy: AnnotationCodingKeys.self, forKey: .annotation) try annotationContainer.encodeIfPresent(segmentDistances, forKey: .segmentDistances) try annotationContainer.encodeIfPresent(expectedSegmentTravelTimes, forKey: .expectedSegmentTravelTimes) try annotationContainer.encodeIfPresent(segmentSpeeds, forKey: .segmentSpeeds) try annotationContainer.encodeIfPresent(segmentCongestionLevels, forKey: .segmentCongestionLevels) + + if let speedLimitDescriptors = segmentMaximumSpeedLimits?.map({ SpeedLimitDescriptor(speed: $0) }) { + try annotationContainer.encode(speedLimitDescriptors, forKey: .segmentMaximumSpeedLimits) + } } } @@ -126,16 +137,39 @@ open class RouteLeg: Codable { Each route step object corresponds to a distinct maneuver and the approach to the next maneuver. - This array is empty if the `includesSteps` property of the original `RouteOptions` object is set to `false`. + This array is empty if the original `RouteOptions` object’s `RouteOptions.includesSteps` property is set to `false`. */ public let steps: [RouteStep] + /** + The ranges of each step’s segments within the overall leg. + + Each range corresponds to an element of the `steps` property. Use this property to safely subscript segment-based properties such as `segmentCongestionLevels` and `segmentMaximumSpeedLimits`. + + This array is empty if the original `RouteOptions` object’s `RouteOptions.includesSteps` property is set to `false`. + */ + public private(set) lazy var segmentRangesByStep: [Range] = { + var segmentRangesByStep: [Range] = [] + var currentStepStartIndex = 0 + for step in steps { + if let coordinates = step.shape?.coordinates { + let stepCoordinateCount = step.maneuverType == .arrive ? coordinates.count : coordinates.dropLast().count + let currentStepEndIndex = currentStepStartIndex.advanced(by: stepCoordinateCount) + segmentRangesByStep.append(currentStepStartIndex..?]? + // MARK: Getting Statistics About the Leg /** @@ -214,6 +259,7 @@ extension RouteLeg: Equatable { lhs.expectedSegmentTravelTimes == rhs.expectedSegmentTravelTimes && lhs.segmentSpeeds == rhs.segmentSpeeds && lhs.segmentCongestionLevels == rhs.segmentCongestionLevels && + lhs.segmentMaximumSpeedLimits == rhs.segmentMaximumSpeedLimits && lhs.name == rhs.name && lhs.distance == rhs.distance && lhs.expectedTravelTime == rhs.expectedTravelTime && diff --git a/Sources/MapboxDirections/RouteStep.swift b/Sources/MapboxDirections/RouteStep.swift index 095d6bc3be3..91d7518e9d4 100644 --- a/Sources/MapboxDirections/RouteStep.swift +++ b/Sources/MapboxDirections/RouteStep.swift @@ -244,6 +244,27 @@ public enum ManeuverDirection: String, Codable { case uTurn = "uturn" } +/** + A road sign design standard. + + A sign standard can affect how a user interface should display information related to the road. For example, a speed limit from the `RouteLeg.segmentMaximumSpeedLimits` property may appear in a different-looking view depending on the `RouteStep.speedLimitSign` property. + */ +public enum SignStandard: String, Codable { + /** + The [Manual on Uniform Traffic Control Devices](https://en.wikipedia.org/wiki/Manual_on_Uniform_Traffic_Control_Devices). + + This standard has been adopted by the United States and Canada, and several other countries have adopted parts of the standard as well. + */ + case mutcd + + /** + The [Vienna Convention on Road Signs and Signals](https://en.wikipedia.org/wiki/Vienna_Convention_on_Road_Signs_and_Signals). + + This standard is prevalent in Europe and parts of Asia and Latin America. Countries in southern Africa and Central America have adopted similar regional standards. + */ + case viennaConvention +} + extension String { internal func tagValues(separatedBy separator: String) -> [String] { return components(separatedBy: separator).map { $0.trimmingCharacters(in: .whitespaces) }.filter { !$0.isEmpty } @@ -370,6 +391,8 @@ open class RouteStep: Codable { case maneuver case pronunciation case rotaryPronunciation = "rotary_pronunciation" + case speedLimitSignStandard = "speedLimitSign" + case speedLimitUnit case transportType = "mode" } @@ -406,10 +429,12 @@ open class RouteStep: Codable { - parameter destinationCodes: Any route reference codes that appear on guide signage for the road leading from this step’s maneuver to the next step’s maneuver. - parameter destinations: Destinations, such as [control cities](https://en.wikipedia.org/wiki/Control_city), that appear on guide signage for the road leading from this step’s maneuver to the next step’s maneuver. - parameter intersections: An array of intersections along the step. + - parameter speedLimitSignStandard: The sign design standard used for speed limit signs along the step. + - parameter speedLimitUnit: The unit of speed limits on speed limit signs along the step. - parameter instructionsSpokenAlongStep: Instructions about the next step’s maneuver, optimized for speech synthesis. - parameter instructionsDisplayedAlongStep: Instructions about the next step’s maneuver, optimized for display in real time. */ - public init(transportType: TransportType, maneuverLocation: CLLocationCoordinate2D, maneuverType: ManeuverType, maneuverDirection: ManeuverDirection? = nil, instructions: String, initialHeading: CLLocationDirection? = nil, finalHeading: CLLocationDirection? = nil, drivingSide: DrivingSide, exitCodes: [String]? = nil, exitNames: [String]? = nil, phoneticExitNames: [String]? = nil, distance: CLLocationDistance, expectedTravelTime: TimeInterval, names: [String]? = nil, phoneticNames: [String]? = nil, codes: [String]? = nil, destinationCodes: [String]? = nil, destinations: [String]? = nil, intersections: [Intersection]? = nil, instructionsSpokenAlongStep: [SpokenInstruction]? = nil, instructionsDisplayedAlongStep: [VisualInstructionBanner]? = nil) { + public init(transportType: TransportType, maneuverLocation: CLLocationCoordinate2D, maneuverType: ManeuverType, maneuverDirection: ManeuverDirection? = nil, instructions: String, initialHeading: CLLocationDirection? = nil, finalHeading: CLLocationDirection? = nil, drivingSide: DrivingSide, exitCodes: [String]? = nil, exitNames: [String]? = nil, phoneticExitNames: [String]? = nil, distance: CLLocationDistance, expectedTravelTime: TimeInterval, names: [String]? = nil, phoneticNames: [String]? = nil, codes: [String]? = nil, destinationCodes: [String]? = nil, destinations: [String]? = nil, intersections: [Intersection]? = nil, speedLimitSignStandard: SignStandard? = nil, speedLimitUnit: UnitSpeed? = nil, instructionsSpokenAlongStep: [SpokenInstruction]? = nil, instructionsDisplayedAlongStep: [VisualInstructionBanner]? = nil) { self.transportType = transportType self.maneuverLocation = maneuverLocation self.maneuverType = maneuverType @@ -429,6 +454,8 @@ open class RouteStep: Codable { self.destinationCodes = destinationCodes self.destinations = destinations self.intersections = nil + self.speedLimitSignStandard = speedLimitSignStandard + self.speedLimitUnit = speedLimitUnit self.instructionsSpokenAlongStep = nil self.instructionsDisplayedAlongStep = nil } @@ -473,6 +500,12 @@ open class RouteStep: Codable { try maneuver.encodeIfPresent(maneuverLocation, forKey: .location) try maneuver.encodeIfPresent(initialHeading, forKey: .initialHeading) try maneuver.encodeIfPresent(finalHeading, forKey: .finalHeading) + + try container.encodeIfPresent(speedLimitSignStandard, forKey: .speedLimitSignStandard) + if let speedLimitUnit = speedLimitUnit, + let unit = SpeedLimitDescriptor.UnitDescriptor(unit: speedLimitUnit) { + try container.encode(unit, forKey: .speedLimitUnit) + } } public required init(from decoder: Decoder) throws { @@ -523,6 +556,9 @@ open class RouteStep: Codable { destinations = road.destinations destinationCodes = road.destinationCodes + speedLimitSignStandard = try container.decodeIfPresent(SignStandard.self, forKey: .speedLimitSignStandard) + speedLimitUnit = (try container.decodeIfPresent(SpeedLimitDescriptor.UnitDescriptor.self, forKey: .speedLimitUnit))?.describedUnit + let type = maneuverType if type == .takeRotary || type == .takeRoundabout { names = road.rotaryNames @@ -704,6 +740,20 @@ open class RouteStep: Codable { */ public let intersections: [Intersection]? + /** + The sign design standard used for speed limit signs along the step. + + This standard affects how corresponding speed limits in the `RouteLeg.segmentMaximumSpeedLimits` property should be displayed. + */ + public let speedLimitSignStandard: SignStandard? + + /** + The unit of speed limits on speed limit signs along the step. + + This standard affects how corresponding speed limits in the `RouteLeg.segmentMaximumSpeedLimits` property should be displayed. + */ + public let speedLimitUnit: UnitSpeed? + // MARK: Getting Details About the Next Maneuver /** @@ -751,6 +801,9 @@ extension RouteStep: Equatable { lhs.destinationCodes == rhs.destinationCodes && lhs.destinations == rhs.destinations && + lhs.speedLimitSignStandard == rhs.speedLimitSignStandard && + lhs.speedLimitUnit == rhs.speedLimitUnit && + lhs.intersections == rhs.intersections && lhs.instructionsSpokenAlongStep == rhs.instructionsSpokenAlongStep && lhs.instructionsDisplayedAlongStep == rhs.instructionsDisplayedAlongStep && diff --git a/Tests/MapboxDirectionsTests/AnnotationTests.swift b/Tests/MapboxDirectionsTests/AnnotationTests.swift index cf805080bc2..d74e777d44d 100644 --- a/Tests/MapboxDirectionsTests/AnnotationTests.swift +++ b/Tests/MapboxDirectionsTests/AnnotationTests.swift @@ -19,7 +19,7 @@ class AnnotationTests: XCTestCase { "steps": "false", "continue_straight": "true", "access_token": BogusToken, - "annotations": "distance,duration,speed,congestion" + "annotations": "distance,duration,speed,congestion,maxspeed" ] stub(condition: isHost("api.mapbox.com") @@ -36,7 +36,7 @@ class AnnotationTests: XCTestCase { options.includesSteps = false options.includesAlternativeRoutes = false options.routeShapeResolution = .full - options.attributeOptions = [.distance, .expectedTravelTime, .speed, .congestionLevel] + options.attributeOptions = [.distance, .expectedTravelTime, .speed, .congestionLevel, .maximumSpeedLimit] var route: Route? let task = Directions(accessToken: BogusToken).calculate(options) { (waypoints, routes, error) in XCTAssertNil(error, "Error: \(error!.localizedDescription)") @@ -57,21 +57,71 @@ class AnnotationTests: XCTestCase { XCTAssertNotNil(route) if let route = route { XCTAssertNotNil(route.shape) - XCTAssertEqual(route.shape?.coordinates.count, 155) - XCTAssertEqual(route.routeIdentifier, "ck2l3ymrx18ws68qo1ukqt9p1") + XCTAssertEqual(route.shape?.coordinates.count, 154) + XCTAssertEqual(route.routeIdentifier, "ck4f22iso03fm78o2f96mt5e9") } if let leg = route?.legs.first { - XCTAssertEqual(leg.segmentDistances?.count, 154) - XCTAssertEqual(leg.segmentSpeeds?.count, 154) - XCTAssertEqual(leg.expectedSegmentTravelTimes?.count, 154) - XCTAssertEqual(leg.segmentCongestionLevels?.count, 154) - XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .unknown), 134) + XCTAssertEqual(leg.segmentDistances?.count, 153) + XCTAssertEqual(leg.segmentSpeeds?.count, 153) + XCTAssertEqual(leg.expectedSegmentTravelTimes?.count, 153) + + XCTAssertEqual(leg.segmentCongestionLevels?.count, 153) + XCTAssertFalse(leg.segmentCongestionLevels?.contains(.unknown) ?? true) XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .low), 0) - XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .moderate), 19) - XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .heavy), 29) + XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .moderate), 21) + XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .heavy), 2) XCTAssertFalse(leg.segmentCongestionLevels?.contains(.severe) ?? true) + + XCTAssertEqual(leg.segmentMaximumSpeedLimits?.count, 153) + XCTAssertEqual(leg.segmentMaximumSpeedLimits?.first, Measurement(value: 48, unit: .kilometersPerHour)) + XCTAssertEqual(leg.segmentMaximumSpeedLimits?.firstIndex(of: nil), 2) + XCTAssertFalse(leg.segmentMaximumSpeedLimits?.contains(Measurement(value: .infinity, unit: .kilometersPerHour)) ?? true) } } + + func testSpeedLimits() { + func assert(_ speedLimitDescriptorJSON: [String: Any], roundTripsWith expectedSpeedLimitDescriptor: SpeedLimitDescriptor) { + let speedLimitDescriptorData = try! JSONSerialization.data(withJSONObject: speedLimitDescriptorJSON, options: []) + var speedLimitDescriptor: SpeedLimitDescriptor? + XCTAssertNoThrow(speedLimitDescriptor = try JSONDecoder().decode(SpeedLimitDescriptor.self, from: speedLimitDescriptorData)) + XCTAssertEqual(speedLimitDescriptor, expectedSpeedLimitDescriptor) + + speedLimitDescriptor = expectedSpeedLimitDescriptor + + let encoder = JSONEncoder() + var encodedData: Data? + XCTAssertNoThrow(encodedData = try encoder.encode(speedLimitDescriptor)) + XCTAssertNotNil(encodedData) + if let encodedData = encodedData { + var encodedSpeedLimitDescriptorJSON: [String: Any?]? + XCTAssertNoThrow(encodedSpeedLimitDescriptorJSON = try JSONSerialization.jsonObject(with: encodedData, options: []) as? [String: Any?]) + XCTAssertNotNil(encodedSpeedLimitDescriptorJSON) + + XCTAssert(JSONSerialization.objectsAreEqual(speedLimitDescriptorJSON, encodedSpeedLimitDescriptorJSON, approximate: true)) + } + } + + XCTAssertEqual(SpeedLimitDescriptor(speed: Measurement(value: 55, unit: .milesPerHour)), + .some(speed: Measurement(value: 55, unit: .milesPerHour))) + XCTAssertEqual(Measurement(speedLimitDescriptor: .some(speed: Measurement(value: 55, unit: .milesPerHour))), + Measurement(value: 55, unit: .milesPerHour)) + assert(["speed": 55.0, "unit": "mph"], roundTripsWith: .some(speed: Measurement(value: 55, unit: .milesPerHour))) + + XCTAssertEqual(SpeedLimitDescriptor(speed: Measurement(value: 80, unit: .kilometersPerHour)), + .some(speed: Measurement(value: 80, unit: .kilometersPerHour))) + XCTAssertEqual(Measurement(speedLimitDescriptor: .some(speed: Measurement(value: 80, unit: .kilometersPerHour))), + Measurement(value: 80, unit: .kilometersPerHour)) + assert(["speed": 80.0, "unit": "km/h"], roundTripsWith: .some(speed: Measurement(value: 80, unit: .kilometersPerHour))) + + XCTAssertEqual(SpeedLimitDescriptor(speed: nil), .unknown) + XCTAssertNil(Measurement(speedLimitDescriptor: .unknown)) + assert(["unknown": true], roundTripsWith: .unknown) + + XCTAssertEqual(SpeedLimitDescriptor(speed: Measurement(value: .infinity, unit: .kilometersPerHour)), .none) + XCTAssertEqual(Measurement(speedLimitDescriptor: .none), + Measurement(value: .infinity, unit: .kilometersPerHour)) + assert(["none": true], roundTripsWith: .none) + } } #endif diff --git a/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json b/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json index 79a36ebef5c..7458a1ca686 100755 --- a/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json +++ b/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json @@ -1 +1 @@ -{"routes":[{"geometry":"w_reF`kgjVPjC@PLCZCDAv@KB?XE`@EPCAQImAKaBAMMcBAUC[ASMmBImAGcAAECc@QiCQqCCYASOyBW}D]qFIsAOqBIyAMcBAYCW_@yFAOAWC]CUc@cHNC`@GB?D@BAZCv@KTCVEXGNCdCYrDc@fBQFAz@KTErBUHCFAFEFGFKLU@AJSDG@C@ADIHMjA{A~@mAJOrAiBtAkBfA{AxAmBt@cALQFIRYbCcDNULOfCiDr@aAj@w@|@kAr@aAbCgDz@iAx@gAd@o@h@s@^c@\\]RU`@Wj@Wh@CI}CAe@KaEAYCqAEwAKeEdDKzAGr@ElAE~BKjFYVAzBKlAEr@ENCJA`@CpDON@L@tAGhCMNA@TFrC@TL?pFWpAGBAB?@A@ALG@A@?BA@?B?@@@@@B?@BN?@@BB?@@","legs":[{"annotation":{"distance":[61.78911719499301,8.005870139027868,7.759236367491888,16.218210852236638,3.5024970090382674,31.200513581321566,2.1296922044642375,15.201992731718047,18.81373666045046,10.25848607823369,8.370665933525826,33.91665057462367,44.16292418618019,5.71461054557965,44.961701083432686,9.34312359965103,12.834233746021397,8.978674003644308,48.699438946727334,35.30623289124711,30.33907283621221,2.5711295719997502,15.682193415494849,61.07751906686583,65.43634942678277,11.757110050097628,8.822345498846637,54.04399846649904,85.0253722355309,107.3065477252522,37.56309620154151,51.10887066054619,39.45635113069872,45.13469133678255,10.87152545777115,11.218562875420577,110.67468814758492,7.745294606313982,10.054979395002901,13.441365621845776,9.899453653643487,130.0897745234779,9.361400186737145,19.29581285450625,2.780657500381912,2.5956907927038757,2.4485571685608805,15.998559299664745,31.749794697887182,11.71392065034445,13.65878604668147,15.27410371531096,8.322252894759458,75.46070687462675,101.70545548543072,58.035805285351024,5.507220756949511,33.68625535817419,12.386818384629517,64.76307027038081,6.131029435168475,4.856565792614805,5.042653827082274,5.838204103483727,6.430704161099769,12.070301310104123,1.7264576473118345,10.966374174746688,4.690062285664435,1.7251438200657419,1.8642728694154649,5.6909983599205916,8.565329863660672,57.550561595270004,49.7085872073418,10.006926395740319,65.76737827104486,66.9331708737475,57.72591261839857,69.14191600033729,42.24814266349312,11.11704088852958,6.254414663677832,15.979691597820226,103.24281834369937,12.711690521702643,10.570960455693678,106.43254410491912,40.77794786767445,35.36882186831562,47.81610361112862,40.77816497948937,104.23082310323272,46.90919967420701,44.75110052426832,30.36563842929047,32.24205532701213,23.49924772735885,21.20339678375213,15.391087011024196,20.82925538207349,26.57837311899057,23.633026596769554,69.65610218424216,17.372295395356478,84.90739912588008,11.55229884626397,36.883733607054054,38.237156565792986,87.12429358191692,92.38902586352489,50.52065949480917,29.87134469553418,42.606188739514366,71.38660462667065,132.6078499394627,12.612783565444955,69.91250563965299,43.13644352701197,29.237837317659345,9.424483651329783,5.911365624296355,18.85658645295614,99.16891737514025,9.325528541113664,7.699257912414974,48.74599424747319,76.86104474199828,8.586515659956389,9.353309270049566,65.62158530817811,9.79152137035292,7.359870632423005,134.86558663123856,46.08468915479624,2.2675599932459636,2.1220692450736567,1.6744149593511495,1.708699266951245,7.683223619704564,1.84658454260673,1.6180500371443371,1.689121948623264,1.4486133063155147,1.5670682617916714,1.4698163191713105,1.5067116313275537,1.5910952866295878,1.559693966970531,6.717659629902365,1.5572084802655337,1.7573463934158997,1.411314740137675,1.7050692236693632],"duration":[7.5,1,1.3,2.8,0.6,5.3,0.4,3.9,4.8,2.6,1,4.1,5.1,0.6,6.2,1.3,1.5,0.9,5,4,3.4,0.3,1.9,7.3,8.4,1.4,1.1,6.3,9,25.8,16.9,6.8,6.2,27.1,6.5,3.4,33.2,2.3,3,3,1.2,17.3,1.6,3.3,0.5,0.4,0.4,2.7,3.5,1.3,1.5,2.5,1.4,12.3,16.6,16.1,1.4,8.7,2.1,13,1.2,1,1,1.2,1.3,2.4,0.3,2.2,0.8,0.3,0.3,1,1.2,7.7,6.9,1.6,9.9,11.5,16,12.4,5.1,1.2,0.7,2.5,16.2,2,1.2,10.4,4.3,3.4,5.1,6.1,15.6,5.3,5,3.4,5.3,3.5,2.8,2.1,2.8,5,3.2,20.9,2.7,13.9,3,8.9,9.2,17.4,15.1,9.6,5.7,8.1,13.5,19.9,1.9,12.6,7.8,5.3,1.4,0.9,2.7,14.3,1.3,1,6.3,9.9,1.1,1.4,9.8,1.5,1.3,23.1,8.7,0.6,0.6,0.5,0.5,2.1,0.5,0.4,0.5,0.4,0.4,0.4,0.4,0.4,0.4,1.9,0.4,0.5,0.4,0.5],"speed":[8.2,8,6,5.8,5.8,5.9,5.3,3.9,3.9,3.9,8.4,8.3,8.7,9.5,7.3,7.2,8.6,10,9.7,8.8,8.9,8.6,8.3,8.4,7.8,8.4,8,8.6,9.4,4.2,2.2,7.5,6.4,1.7,1.7,3.3,3.3,3.4,3.4,4.5,8.2,7.5,5.9,5.8,5.6,6.5,6.1,5.9,9.1,9,9.1,6.1,5.9,6.1,6.1,3.6,3.9,3.9,5.9,5,5.1,4.9,5,4.9,4.9,5,5.8,5,5.9,5.8,6.2,5.7,7.1,7.5,7.2,6.3,6.6,5.8,3.6,5.6,8.3,9.3,8.9,6.4,6.4,6.4,8.8,10.2,9.5,10.4,9.4,6.7,6.7,8.9,9,8.9,6.1,6.7,7.6,7.3,7.4,5.3,7.4,3.3,6.4,6.1,3.9,4.1,4.2,5,6.1,5.3,5.2,5.3,5.3,6.7,6.6,5.5,5.5,5.5,6.7,6.6,7,6.9,7.2,7.7,7.7,7.8,7.8,6.7,6.7,6.5,5.7,5.8,5.3,3.8,3.5,3.3,3.4,3.7,3.7,4,3.4,3.6,3.9,3.7,3.8,4,3.9,3.5,3.9,3.5,3.5,3.4],"congestion":["low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","moderate","moderate","moderate","moderate","moderate","low","low","low","low","low","heavy","low","low","moderate","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","heavy","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","moderate","low","heavy","heavy","heavy","low","low","low","low","low","low","low","low","low","low","low","moderate","moderate","low","low","low","low","low","low","low","low","low","low","heavy","low","low","low","moderate","moderate","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","unknown","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low"]},"summary":"","weight":1285.2,"duration":873,"steps":[],"distance":4667.2}],"weight_name":"routability","weight":1285.2,"duration":873,"distance":4667.2}],"waypoints":[{"distance":0.11098979064122912,"name":"Turk Street","location":[-122.431373,37.780601]},{"distance":0,"name":"Vermont Street","location":[-122.404058,37.758859]}],"code":"Ok","uuid":"ck2l3ymrx18ws68qo1ukqt9p1"} \ No newline at end of file +{"routes":[{"geometry":"w_reF`kgjVPjC@PLCZCDAv@KB?XE`@EPCAQImAKaBAMMcBAUC[ASMmBImAGcAAECc@QiCQqCCYASOyBW}D]qFIsAOqBIyAMcBAYCW_@yFAOAWC]CUc@cHNC`@GB?D@BAZCv@KTCVEXGNCdCYrDc@fBQFAz@KTErBUHCFAFEFGFKLU@AJSDG@C@ADIHMjA{A~@mAJOrAiBtAkBfA{AxAmBt@cALQFIRYbCcDNULOfCiDr@aAj@w@|@kAr@aAbCgDz@iAx@gAd@o@h@s@^c@\\]RU`@Wj@Wh@CI}CAe@KaEAYCqAEwAKeEdDKzAGr@ElAE~BKjFYVAzBKlAEr@ENCJA`@CpDON@L@tAGhCMNAL?`AEnDQJ~DpAGBAB?@A@ALG@A@?BA@?B?@@@@@B?@BN?@@BB?@@","legs":[{"annotation":{"distance":[61.78911719499301,8.005870139027868,7.759236367491888,16.218210852236638,3.5024970090382674,31.200513581321566,2.1296922044642375,15.201992731718047,18.81373666045046,10.25848607823369,8.370665933525826,33.91665057462367,44.16292418618019,5.71461054557965,44.961701083432686,9.34312359965103,12.834233746021397,8.978674003644308,48.699438946727334,35.30623289124711,30.33907283621221,2.5711295719997502,15.682193415494849,61.07751906686583,65.43634942678277,11.757110050097628,8.822345498846637,54.04399846649904,85.0253722355309,107.3065477252522,37.56309620154151,51.10887066054619,39.45635113069872,45.13469133678255,10.87152545777115,11.218562875420577,110.67468814758492,7.745294606313982,10.054979395002901,13.441365621845776,9.899453653643487,130.0897745234779,9.361400186737145,19.29581285450625,2.780657500381912,2.5956907927038757,2.4485571685608805,15.998559299664745,31.749794697887182,11.71392065034445,13.65878604668147,15.27410371531096,8.322252894759458,75.46070687462675,101.70545548543072,58.035805285351024,5.507220756949511,33.68625535817419,12.386818384629517,64.76307027038081,6.131029435168475,4.856565792614805,5.042653827082274,5.838204103483727,6.430704161099769,12.070301310104123,1.7264576473118345,10.966374174746688,4.690062285664435,1.7251438200657419,1.8642728694154649,5.6909983599205916,8.565329863660672,57.550561595270004,49.7085872073418,10.006926395740319,65.76737827104486,66.9331708737475,57.72591261839857,69.14191600033729,42.24814266349312,11.11704088852958,6.254414663677832,15.979691597820226,103.24281834369937,12.711690521702643,10.570960455693678,106.43254410491912,40.77794786767445,35.36882186831562,47.81610361112862,40.77816497948937,104.23082310323272,46.90919967420701,44.75110052426832,30.36563842929047,32.24205532701213,23.49924772735885,21.20339678375213,15.391087011024196,20.82925538207349,26.57837311899057,23.633026596769554,69.65610218424216,17.372295395356478,84.90739912588008,11.55229884626397,36.883733607054054,38.237156565792986,87.12429358191692,92.38902586352489,50.52065949480917,29.87134469553418,42.606188739514366,71.38660462667065,132.6078499394627,12.612783565444955,69.91250563965299,43.13644352701197,29.237837317659345,9.424483651329783,5.911365624296355,18.85658645295614,99.16891737514025,9.325528541113664,7.699257912414974,48.74599424747319,76.86104474199828,8.586515659956389,8.031913909220021,36.69484164354201,97.82472538592995,84.65444720167741,46.08468915479624,2.2675599932459636,2.1220692450736567,1.6744149593511495,1.708699266951245,7.683223619704564,1.84658454260673,1.6180500371443371,1.689121948623264,1.4486133063155147,1.5670682617916714,1.4698163191713105,1.5067116313275537,1.5910952866295878,1.559693966970531,6.717659629902365,1.5572084802655337,1.7573463934158997,1.411314740137675,1.7050692236693632],"duration":[7.7,1,4.7,9.7,2.1,18.7,1.3,2.5,3.1,1.7,1.1,4.5,5,0.6,5.4,1.1,1.9,0.9,4.9,3.3,3,0.3,1.5,5.9,12.4,2.1,1,6.1,8.3,13.3,19.3,6.1,5.5,32.5,7.8,2.1,21,1.5,2,2.4,2.1,27.5,2.2,4.6,0.7,0.6,0.6,3.8,6.7,2.5,2.9,2.7,1.5,13.6,18.3,11.6,2.8,17.3,2.3,23.3,2.2,1.7,1.8,2.1,2.3,4.3,0.6,3.9,0.7,0.3,0.3,0.9,1,6.7,5.8,1.3,9.5,6.3,5.9,8,4.2,1.1,0.6,1.6,10,1.2,1.2,12.4,4.1,3.7,5.7,4.9,11.7,8.9,6.4,5.8,5.5,4,3.3,2.4,3.3,5.6,3.3,10,2.7,18,3,6.6,6.9,18.4,11.5,9.1,6.7,9.6,16.1,21.7,2.1,11.4,7.1,4.8,1.7,1.1,3.4,17.9,1.7,1.3,8.4,13.2,1.5,0.9,4,13,15.2,7.9,0.7,0.6,0.5,0.5,2.3,0.6,0.5,0.5,0.4,0.5,0.4,0.5,0.5,0.5,2,0.5,0.5,0.4,0.5],"speed":[8,8,1.7,1.7,1.7,1.7,1.6,6.1,6.1,6,7.6,7.5,8.8,9.5,8.3,8.5,6.8,10,9.9,10.7,10.1,8.6,10.5,10.4,5.3,5.6,8.8,8.9,10.2,8.1,1.9,8.4,7.2,1.4,1.4,5.3,5.3,5.2,5,5.6,4.7,4.7,4.3,4.2,4,4.3,4.1,4.2,4.7,4.7,4.7,5.7,5.5,5.5,5.6,5,2,1.9,5.4,2.8,2.8,2.9,2.8,2.8,2.8,2.8,2.9,2.8,6.7,5.8,6.2,6.3,8.6,8.6,8.6,7.7,6.9,10.6,9.8,8.6,10.1,10.1,10.4,10,10.3,10.6,8.8,8.6,9.9,9.6,8.4,8.3,8.9,5.3,7,5.2,5.9,5.9,6.4,6.4,6.3,4.7,7.2,7,6.4,4.7,3.9,5.6,5.5,4.7,8,5.6,4.5,4.4,4.4,6.1,6,6.1,6.1,6.1,5.5,5.4,5.5,5.5,5.5,5.9,5.8,5.8,5.7,8.9,9.2,7.5,5.6,5.8,3.2,3.5,3.3,3.4,3.3,3.1,3.2,3.4,3.6,3.1,3.7,3,3.2,3.1,3.4,3.1,3.5,3.5,3.4],"congestion":["low","low","heavy","heavy","heavy","heavy","heavy","low","low","low","low","low","low","low","low","low","low","low","low","low","low","moderate","low","low","low","low","low","low","low","low","moderate","low","low","low","low","low","low","low","low","low","low","low","heavy","heavy","heavy","heavy","heavy","heavy","low","low","low","low","moderate","moderate","low","moderate","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","moderate","low","low","low","low","low","low","low","low","low","low","low","moderate","low","low","low","low","low","moderate","low","heavy","low","low","low","low","moderate","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low"],"maxspeed":[{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":40,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"speed":48,"unit":"km/h"},{"unknown":true},{"speed":40,"unit":"km/h"},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true},{"unknown":true}]},"summary":"","weight":1309.6,"duration":897,"steps":[],"distance":4667.4}],"weight_name":"routability","weight":1309.6,"duration":897,"distance":4667.4}],"waypoints":[{"distance":0.11098979064122912,"name":"Turk Street","location":[-122.431373,37.780601]},{"distance":0,"name":"Vermont Street","location":[-122.404058,37.758859]}],"code":"Ok","uuid":"ck4f22iso03fm78o2f96mt5e9"} \ No newline at end of file diff --git a/Tests/MapboxDirectionsTests/RouteLegTests.swift b/Tests/MapboxDirectionsTests/RouteLegTests.swift new file mode 100644 index 00000000000..853101cea9a --- /dev/null +++ b/Tests/MapboxDirectionsTests/RouteLegTests.swift @@ -0,0 +1,30 @@ +import XCTest +import CoreLocation +import Turf +@testable import MapboxDirections + +class RouteLegTests: XCTestCase { + func testSegmentRanges() { + let departureStep = RouteStep(transportType: .automobile, maneuverLocation: CLLocationCoordinate2D(latitude: 0, longitude: 0), maneuverType: .depart, instructions: "Depart", drivingSide: .right, distance: 10, expectedTravelTime: 10) + departureStep.shape = LineString([ + CLLocationCoordinate2D(latitude: 0, longitude: 0), + CLLocationCoordinate2D(latitude: 1, longitude: 1), + ]) + let turnStep = RouteStep(transportType: .automobile, maneuverLocation: CLLocationCoordinate2D(latitude: 1, longitude: 1), maneuverType: .turn, maneuverDirection: .left, instructions: "Turn left at Albuquerque", drivingSide: .right, distance: 10, expectedTravelTime: 10) + turnStep.shape = LineString([ + CLLocationCoordinate2D(latitude: 1, longitude: 1), + CLLocationCoordinate2D(latitude: 2, longitude: 2), + CLLocationCoordinate2D(latitude: 3, longitude: 3), + CLLocationCoordinate2D(latitude: 4, longitude: 4), + ]) + let arrivalStep = RouteStep(transportType: .automobile, maneuverLocation: CLLocationCoordinate2D(latitude: 4, longitude: 4), maneuverType: .arrive, instructions: "Arrive at Elmer’s House", drivingSide: .right, distance: 0, expectedTravelTime: 0) + let leg = RouteLeg(steps: [departureStep, turnStep, arrivalStep], name: "", distance: 10, expectedTravelTime: 10, profileIdentifier: .automobile) + leg.segmentDistances = [ + 10, + 10, 20, 30, + ] + XCTAssertEqual(leg.segmentRangesByStep.count, leg.steps.count) + XCTAssertEqual(leg.segmentRangesByStep, [0..<1, 1..<4, 4..<4]) + XCTAssertEqual(leg.segmentRangesByStep.last?.upperBound, leg.segmentDistances?.count) + } +}