diff --git a/MapboxDirections.xcodeproj/project.pbxproj b/MapboxDirections.xcodeproj/project.pbxproj index bf45e233c..b21a6563a 100644 --- a/MapboxDirections.xcodeproj/project.pbxproj +++ b/MapboxDirections.xcodeproj/project.pbxproj @@ -34,6 +34,10 @@ 2B28E22327EDB2AA0029E4C1 /* ForeignMemberContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */; }; 2B28E22427EDB2AA0029E4C1 /* ForeignMemberContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */; }; 2B28E22527EDB2AA0029E4C1 /* ForeignMemberContainerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */; }; + 2B36B3E22980202600ED1E47 /* TrafficTendency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */; }; + 2B36B3E32980202600ED1E47 /* TrafficTendency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */; }; + 2B36B3E42980202600ED1E47 /* TrafficTendency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */; }; + 2B36B3E52980202600ED1E47 /* TrafficTendency.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */; }; 2B39DD40270F034700ED68E4 /* CodingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B39DD3F270F034700ED68E4 /* CodingOperation.swift */; }; 2B4383022549C22700A3E38B /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B4383002549C22700A3E38B /* main.swift */; }; 2B46024528EDB1670008A624 /* CustomValueOptionSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */; }; @@ -551,6 +555,7 @@ 2B0BA057294B544E00402F81 /* MatrixError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatrixError.swift; sourceTree = ""; }; 2B0BA068294B546300402F81 /* MatrixTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MatrixTests.swift; sourceTree = ""; }; 2B28E22227EDB2A90029E4C1 /* ForeignMemberContainerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForeignMemberContainerTests.swift; sourceTree = ""; }; + 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrafficTendency.swift; sourceTree = ""; }; 2B39DD3F270F034700ED68E4 /* CodingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CodingOperation.swift; path = Sources/MapboxDirectionsCLI/CodingOperation.swift; sourceTree = ""; }; 2B4383002549C22700A3E38B /* main.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = main.swift; path = Sources/MapboxDirectionsCLI/main.swift; sourceTree = ""; }; 2B46024428EDB1670008A624 /* CustomValueOptionSet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomValueOptionSet.swift; sourceTree = ""; }; @@ -945,6 +950,7 @@ DAC05F151CFBFAC400FA0071 /* Waypoint.swift */, F4CF2C562523B66300A6D0B6 /* TollCollection.swift */, 2B79D3E528E5EB3900B3F127 /* TollPrice.swift */, + 2B36B3E12980202600ED1E47 /* TrafficTendency.swift */, F4F508372524D6F10044F2D0 /* RestStop.swift */, F4F5084A2524DC280044F2D0 /* AdministrativeRegion.swift */, F457FA79252B9E29007DAEB1 /* Incident.swift */, @@ -1530,6 +1536,7 @@ files = ( 2B9F3892272AE23A001DBA12 /* Isochrones.swift in Sources */, 43538E3923ED463100E010D4 /* ResponseDisposition.swift in Sources */, + 2B36B3E32980202600ED1E47 /* TrafficTendency.swift in Sources */, 431E93C0234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9B2019167C001F9261 /* Match.swift in Sources */, 2B5407EE2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, @@ -1639,6 +1646,7 @@ files = ( 2B9F3893272AE23A001DBA12 /* Isochrones.swift in Sources */, 43538E3A23ED463200E010D4 /* ResponseDisposition.swift in Sources */, + 2B36B3E42980202600ED1E47 /* TrafficTendency.swift in Sources */, 431E93C1234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9C2019167D001F9261 /* Match.swift in Sources */, 2B5407EF2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, @@ -1748,6 +1756,7 @@ files = ( 2B9F3894272AE23A001DBA12 /* Isochrones.swift in Sources */, 43538E3B23ED463400E010D4 /* ResponseDisposition.swift in Sources */, + 2B36B3E52980202600ED1E47 /* TrafficTendency.swift in Sources */, 431E93C2234664A200A71B44 /* DrivingSide.swift in Sources */, C5DAAC9D2019167E001F9261 /* Match.swift in Sources */, 2B5407F02451B17E006C820B /* RouteRefreshResponse.swift in Sources */, @@ -1819,6 +1828,7 @@ files = ( 2B9F3891272AE23A001DBA12 /* Isochrones.swift in Sources */, 43538E3823ED463100E010D4 /* ResponseDisposition.swift in Sources */, + 2B36B3E22980202600ED1E47 /* TrafficTendency.swift in Sources */, 431E93BF234664A200A71B44 /* DrivingSide.swift in Sources */, C51538CC1E807FF00093FF3E /* AttributeOptions.swift in Sources */, 2B5407ED2451B17E006C820B /* RouteRefreshResponse.swift in Sources */, diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift index b049893c3..ae8fd45fb 100644 --- a/Sources/MapboxDirections/AttributeOptions.swift +++ b/Sources/MapboxDirections/AttributeOptions.swift @@ -75,6 +75,12 @@ public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { */ public static let numericCongestionLevel = AttributeOptions(rawValue: 1 << 6) + /** + :nodoc: + The tendency value conveys the changing state of traffic congestion (increasing, decreasing, constant etc). + */ + public static let trafficTendency = AttributeOptions(rawValue: 1 << 7) + /** Creates an AttributeOptions from the given description strings. */ @@ -96,6 +102,8 @@ public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { attributeOptions.update(with: .maximumSpeedLimit) case "congestion_numeric": attributeOptions.update(with: .numericCongestionLevel) + case "traffic_tendency": + attributeOptions.update(with: .trafficTendency) case "": continue default: @@ -128,6 +136,9 @@ public struct AttributeOptions: CustomValueOptionSet, CustomStringConvertible { if contains(.numericCongestionLevel) { descriptions.append("congestion_numeric") } + if contains(.trafficTendency) { + descriptions.append("traffic_tendency") + } for (key, value) in customOptionsByRawValue { if rawValue & key != 0 { descriptions.append(value) diff --git a/Sources/MapboxDirections/RouteLeg.swift b/Sources/MapboxDirections/RouteLeg.swift index bb9082021..509265559 100644 --- a/Sources/MapboxDirections/RouteLeg.swift +++ b/Sources/MapboxDirections/RouteLeg.swift @@ -270,6 +270,12 @@ open class RouteLeg: Codable, ForeignMemberContainerClass { */ open var closures: [Closure]? + /** + :nodoc: + The tendency value conveys the changing state of traffic congestion (increasing, decreasing, constant etc). + */ + open var trafficTendencies: [TrafficTendency]? + /** The full collection of attributes along the leg. */ @@ -280,7 +286,8 @@ open class RouteLeg: Codable, ForeignMemberContainerClass { segmentSpeeds: segmentSpeeds, segmentCongestionLevels: segmentCongestionLevels, segmentNumericCongestionLevels: segmentNumericCongestionLevels, - segmentMaximumSpeedLimits: segmentMaximumSpeedLimits) + segmentMaximumSpeedLimits: segmentMaximumSpeedLimits, + trafficTendencies: trafficTendencies) } set { segmentDistances = newValue.segmentDistances @@ -289,6 +296,7 @@ open class RouteLeg: Codable, ForeignMemberContainerClass { segmentCongestionLevels = newValue.segmentCongestionLevels segmentNumericCongestionLevels = newValue.segmentNumericCongestionLevels segmentMaximumSpeedLimits = newValue.segmentMaximumSpeedLimits + trafficTendencies = newValue.trafficTendencies } } @@ -301,6 +309,7 @@ open class RouteLeg: Codable, ForeignMemberContainerClass { segmentCongestionLevels?.replaceIfPossible(subrange: refreshRange, with: newAttributes.segmentCongestionLevels) segmentNumericCongestionLevels?.replaceIfPossible(subrange: refreshRange, with: newAttributes.segmentNumericCongestionLevels) segmentMaximumSpeedLimits?.replaceIfPossible(subrange: refreshRange, with: newAttributes.segmentMaximumSpeedLimits) + trafficTendencies?.replaceIfPossible(subrange: refreshRange, with: newAttributes.trafficTendencies) } private func adjustShapeIndexRange(_ range: Range, startLegShapeIndex: Int) -> Range { @@ -424,6 +433,7 @@ extension RouteLeg: Equatable { lhs.segmentCongestionLevels == rhs.segmentCongestionLevels && lhs.segmentNumericCongestionLevels == rhs.segmentNumericCongestionLevels && lhs.segmentMaximumSpeedLimits == rhs.segmentMaximumSpeedLimits && + lhs.trafficTendencies == rhs.trafficTendencies && lhs.name == rhs.name && lhs.distance == rhs.distance && lhs.expectedTravelTime == rhs.expectedTravelTime && diff --git a/Sources/MapboxDirections/RouteLegAttributes.swift b/Sources/MapboxDirections/RouteLegAttributes.swift index 4d032c7da..bc8899ece 100644 --- a/Sources/MapboxDirections/RouteLegAttributes.swift +++ b/Sources/MapboxDirections/RouteLegAttributes.swift @@ -65,6 +65,12 @@ extension RouteLeg { This property is set if the `RouteOptions.attributeOptions` property contains `AttributeOptions.maximumSpeedLimit`. */ public var segmentMaximumSpeedLimits: [Measurement?]? + + /** + :nodoc: + The tendency value conveys the changing state of traffic congestion (increasing, decreasing, constant etc). + */ + public var trafficTendencies: [TrafficTendency]? } } @@ -76,6 +82,7 @@ extension RouteLeg.Attributes: Codable { case segmentCongestionLevels = "congestion" case segmentNumericCongestionLevels = "congestion_numeric" case segmentMaximumSpeedLimits = "maxspeed" + case trafficTendencies = "traffic_tendency" } public init(from decoder: Decoder) throws { @@ -93,6 +100,8 @@ extension RouteLeg.Attributes: Codable { segmentMaximumSpeedLimits = nil } + trafficTendencies = try container.decodeIfPresent([TrafficTendency].self, forKey: .trafficTendencies) + try decodeForeignMembers(notKeyedBy: CodingKeys.self, with: decoder) } @@ -109,6 +118,8 @@ extension RouteLeg.Attributes: Codable { try container.encode(speedLimitDescriptors, forKey: .segmentMaximumSpeedLimits) } + try container.encodeIfPresent(trafficTendencies, forKey: .trafficTendencies) + try encodeForeignMembers(notKeyedBy: CodingKeys.self, to: encoder) } @@ -121,6 +132,7 @@ extension RouteLeg.Attributes: Codable { segmentSpeeds == nil && segmentCongestionLevels == nil && segmentNumericCongestionLevels == nil && - segmentMaximumSpeedLimits == nil + segmentMaximumSpeedLimits == nil && + trafficTendencies == nil } } diff --git a/Sources/MapboxDirections/TrafficTendency.swift b/Sources/MapboxDirections/TrafficTendency.swift new file mode 100644 index 000000000..7c40bb427 --- /dev/null +++ b/Sources/MapboxDirections/TrafficTendency.swift @@ -0,0 +1,20 @@ +import Foundation + +/// :nodoc: +/// The tendency value conveys the changing state of traffic congestion (increasing, decreasing, constant etc). +/// +/// New values could be introduced in the future without an API version change. +public enum TrafficTendency: Int, Codable, CaseIterable { + /// Congestion tendency is unknown. + case unknown = 0 + /// Congestion tendency is not changing. + case constant = 1 + /// Congestion tendency is increasing. + case increasing = 2 + /// Congestion tendency is decreasing. + case decreasing = 3 + /// Congestion tendency is rapidly increasing. + case rapidlyIncreasing = 4 + /// Congestion tendency is rapidly decreasing. + case rapidlyDecreasing = 5 +} diff --git a/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshResponse.json b/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshResponse.json index 21696d317..51be13f6c 100644 --- a/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshResponse.json +++ b/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshResponse.json @@ -6,7 +6,7 @@ "closures": [ { "geometry_index_start": 8, - "geometry_index_end": 20, + "geometry_index_end": 20 } ], "incidents": [ @@ -22,7 +22,10 @@ "alertc_codes": [ 1 ], - "lanes_blocked": ["RIGHT", "SIDE"], + "lanes_blocked": [ + "RIGHT", + "SIDE" + ], "geometry_index_start": 10, "geometry_index_end": 15, "iso_3166_1_alpha3": "DEU", @@ -39,6 +42,69 @@ } ], "annotation": { + "traffic_tendency": [ + 0, + 2, + 2, + 1, + 4, + 4, + 1, + 4, + 0, + 4, + 4, + 4, + 5, + 3, + 4, + 4, + 3, + 0, + 3, + 2, + 5, + 2, + 2, + 1, + 3, + 3, + 3, + 2, + 4, + 3, + 5, + 4, + 3, + 1, + 1, + 4, + 1, + 5, + 2, + 2, + 3, + 5, + 4, + 2, + 1, + 4, + 0, + 1, + 1, + 3, + 0, + 5, + 0, + 1, + 5, + 3, + 2, + 3, + 3, + 5, + 3 + ], "duration": [ 34.3, 3.5, @@ -391,7 +457,7 @@ "closures": [ { "geometry_index_start": 6, - "geometry_index_end": 18, + "geometry_index_end": 18 } ], "incidents": [ @@ -415,6 +481,48 @@ } ], "annotation": { + "traffic_tendency": [ + 4, + 3, + 1, + 3, + 3, + 4, + 5, + 3, + 5, + 0, + 2, + 5, + 0, + 1, + 1, + 4, + 0, + 1, + 5, + 4, + 3, + 0, + 0, + 1, + 1, + 3, + 4, + 5, + 5, + 5, + 4, + 3, + 3, + 5, + 5, + 1, + 0, + 2, + 0, + 1 + ], "duration": [ 4.9, 4.7, diff --git a/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshRoute.json b/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshRoute.json index 3be94c187..a03aa0ba7 100644 --- a/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshRoute.json +++ b/Tests/MapboxDirectionsTests/Fixtures/RouteRefresh/routeRefreshRoute.json @@ -5,6 +5,69 @@ "legs": [ { "annotation": { + "traffic_tendency": [ + 2, + 4, + 1, + 2, + 1, + 2, + 5, + 1, + 0, + 2, + 5, + 1, + 5, + 2, + 3, + 0, + 5, + 2, + 1, + 3, + 0, + 1, + 3, + 1, + 0, + 5, + 1, + 2, + 5, + 4, + 5, + 1, + 3, + 4, + 4, + 2, + 2, + 2, + 4, + 5, + 5, + 0, + 0, + 0, + 4, + 0, + 1, + 0, + 0, + 0, + 0, + 4, + 5, + 5, + 3, + 1, + 5, + 2, + 0, + 5, + 4 + ], "duration": [ 14.3, 3.5, @@ -1118,14 +1181,60 @@ "distance": 0, "speedLimitSign": "mutcd", "speedLimitUnit": "mph", - "voiceInstructions": [], - "bannerInstructions": [] + "voiceInstructions": [ + + ], + "bannerInstructions": [ + + ] } ], "distance": 1440.6 }, { "annotation": { + "traffic_tendency": [ + 3, + 4, + 1, + 4, + 3, + 1, + 4, + 3, + 3, + 2, + 0, + 1, + 2, + 0, + 1, + 3, + 1, + 3, + 4, + 5, + 5, + 5, + 4, + 3, + 4, + 0, + 2, + 2, + 2, + 1, + 1, + 0, + 3, + 2, + 0, + 1, + 0, + 2, + 2, + 0 + ], "duration": [ 2.3, 4.7, @@ -1863,8 +1972,12 @@ "distance": 0, "speedLimitSign": "mutcd", "speedLimitUnit": "mph", - "voiceInstructions": [], - "bannerInstructions": [] + "voiceInstructions": [ + + ], + "bannerInstructions": [ + + ] } ], "distance": 1217.2 diff --git a/Tests/MapboxDirectionsTests/RouteRefreshTests.swift b/Tests/MapboxDirectionsTests/RouteRefreshTests.swift index cc3709449..6c8faea6e 100644 --- a/Tests/MapboxDirectionsTests/RouteRefreshTests.swift +++ b/Tests/MapboxDirectionsTests/RouteRefreshTests.swift @@ -322,6 +322,9 @@ class RouteRefreshTests: XCTestCase { ], "congestion_numeric": [ 100, + ], + "traffic_tendency": [ + 0, ] ], ], @@ -340,13 +343,19 @@ class RouteRefreshTests: XCTestCase { XCTAssertEqual(leg.attributes.segmentSpeeds, [0]) XCTAssertEqual(leg.attributes.segmentCongestionLevels, [.severe]) XCTAssertEqual(leg.attributes.segmentNumericCongestionLevels, [100]) + XCTAssertEqual(leg.attributes.trafficTendencies, [.unknown]) XCTAssertNil(leg.attributes.segmentMaximumSpeedLimits) } } func testEncoding() { let leg = RefreshedRouteLeg(attributes: - .init(segmentDistances: [0], expectedSegmentTravelTimes: [0], segmentSpeeds: [0], segmentCongestionLevels: [CongestionLevel.severe], segmentMaximumSpeedLimits: [Measurement(value: 1, unit: UnitSpeed.milesPerHour)])) + .init(segmentDistances: [0], + expectedSegmentTravelTimes: [0], + segmentSpeeds: [0], + segmentCongestionLevels: [CongestionLevel.severe], + segmentMaximumSpeedLimits: [Measurement(value: 1, unit: UnitSpeed.milesPerHour)], + trafficTendencies: [.constant])) let route = RefreshedRoute(legs: [leg]) var encodedRouteData: Data? @@ -369,6 +378,7 @@ class RouteRefreshTests: XCTestCase { XCTAssertEqual(annotationJSON["duration"] as? [TimeInterval], [0]) XCTAssertEqual(annotationJSON["speed"] as? [LocationSpeed], [0]) XCTAssertEqual(annotationJSON["congestion"] as? [String], ["severe"]) + XCTAssertEqual(annotationJSON["traffic_tendency"] as? [Int], [TrafficTendency.constant.rawValue]) let maxspeedsJSON = annotationJSON["maxspeed"] as? [[String: Any?]] XCTAssertNotNil(maxspeedsJSON)