Repository: mapbox/mapbox-directions-swift
Pull request #588 (2021-09-08 09:43:32)

Don't append service sku token if it doesn't match access token used …
…in the Directions request


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e7dc1738..fad97f69 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
 * Removed the `DirectionsResult.routeIdentifier` property. Use the `RouteResponse.identifier` property in conjunction with an index into the `RouteResponse.routes` array instead. ([#562](https://github.com/mapbox/mapbox-directions-swift/pull/562))
 * Fixed an issue where RouteStep.exitIndex was always unset. ([#567](https://github.com/mapbox/mapbox-directions-swift/pull/567))
 * Added the `Waypoint.allowsSnappingToClosedRoad` property to allow snapping the waypoint’s location to a closed part of a road. ([#583](https://github.com/mapbox/mapbox-directions-swift/pull/583))
+* Added `AttributeOptions.numericCongestionLevel`, `RouteLeg.segmentNumericCongestionLevels`, `RouteLeg.Attributes.segmentNumericCongestionLevels` and `NumericCongestionLevel` to support receiving the numeric value for congestion level along each segment of a `RouteLeg`. ([#575](https://github.com/mapbox/mapbox-directions-swift/pull/575))
 
 ## v1.2.0
 
diff --git a/Sources/MapboxDirections/AttributeOptions.swift b/Sources/MapboxDirections/AttributeOptions.swift
index a0deed89..40097885 100644
--- a/Sources/MapboxDirections/AttributeOptions.swift
+++ b/Sources/MapboxDirections/AttributeOptions.swift
@@ -48,6 +48,15 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible {
      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)
+
+    /**
+     Traffic congestion level in numeric form.
+
+     When this attribute is specified, the `RouteLeg.numericCongestionLevels` property contains one value for each segment in the leg’s full geometry.
+
+     This attribute requires `DirectionsProfileIdentifier.automobileAvoidingTraffic`. Any other profile identifier produces `nil` for each segment along the route.
+     */
+    public static let numericCongestionLevel = AttributeOptions(rawValue: 1 << 6)
     
     /**
      Creates an AttributeOptions from the given description strings.
@@ -66,6 +75,8 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible {
                 attributeOptions.update(with: .congestionLevel)
             case "maxspeed":
                 attributeOptions.update(with: .maximumSpeedLimit)
+            case "congestion_numeric":
+                attributeOptions.update(with: .numericCongestionLevel)
             case "":
                 continue
             default:
@@ -92,6 +103,9 @@ public struct AttributeOptions: OptionSet, CustomStringConvertible {
         if contains(.maximumSpeedLimit) {
             descriptions.append("maxspeed")
         }
+        if contains(.numericCongestionLevel) {
+            descriptions.append("congestion_numeric")
+        }
         return descriptions.joined(separator: ",")
     }
 }
diff --git a/Sources/MapboxDirections/Congestion.swift b/Sources/MapboxDirections/Congestion.swift
index 7daedd39..c5553a23 100644
--- a/Sources/MapboxDirections/Congestion.swift
+++ b/Sources/MapboxDirections/Congestion.swift
@@ -37,3 +37,8 @@ public enum CongestionLevel: String, Codable, CaseIterable {
      */
     case severe
 }
+
+/**
+ `NumericCongestionLevel` is the level of traffic congestion along a road segment in numeric form, from 0-100. A value of 0 indicates no congestion, a value of 100 indicates maximum congestion.
+ */
+public typealias NumericCongestionLevel = Int
diff --git a/Sources/MapboxDirections/DirectionsCredentials.swift b/Sources/MapboxDirections/DirectionsCredentials.swift
index 79c3d01a..b4f01b40 100644
--- a/Sources/MapboxDirections/DirectionsCredentials.swift
+++ b/Sources/MapboxDirections/DirectionsCredentials.swift
@@ -26,7 +26,12 @@ public struct DirectionsCredentials: Equatable {
         #if !os(Linux)
         guard let mbx: AnyClass = NSClassFromString("MBXAccounts") else { return nil }
         guard mbx.responds(to: Selector(("serviceSkuToken"))) else { return nil }
-        return mbx.value(forKeyPath: "serviceSkuToken") as? String
+        guard let serviceSkuToken = mbx.value(forKeyPath: "serviceSkuToken") as? String,
+              let serviceAccessToken = mbx.value(forKeyPath: "serviceAccessToken") as? String,
+              serviceAccessToken == accessToken else {
+                  return nil
+              }
+        return serviceSkuToken
         #else
         return nil
         #endif
diff --git a/Sources/MapboxDirections/RouteLeg.swift b/Sources/MapboxDirections/RouteLeg.swift
index 9cc35937..86597108 100644
--- a/Sources/MapboxDirections/RouteLeg.swift
+++ b/Sources/MapboxDirections/RouteLeg.swift
@@ -45,6 +45,7 @@ open class RouteLeg: Codable {
         expectedSegmentTravelTimes = nil
         segmentSpeeds = nil
         segmentCongestionLevels = nil
+        segmentNumericCongestionLevels = nil
     }
     
     /**
@@ -202,6 +203,19 @@ open class RouteLeg: Codable {
      This property is set if the `RouteOptions.attributeOptions` property contains `AttributeOptions.congestionLevel`.
      */
     open var segmentCongestionLevels: [CongestionLevel]?
+
+    /**
+     An array containing the traffic congestion level along each road segment in the route leg geometry in numeric form.
+
+     Entries may be `nil` if congestion on that segment is not known.
+
+     Traffic data is available in [a number of countries and territories worldwide](https://docs.mapbox.com/help/how-mapbox-works/directions/#traffic-data).
+
+     You can color-code a route line according to the congestion level along each segment of the route.
+
+     This property is set if the `RouteOptions.attributeOptions` property contains `AttributeOptions.numericCongestionLevel`.
+     */
+    open var segmentNumericCongestionLevels: [NumericCongestionLevel?]?
     
     /**
      An array containing the maximum speed limit along each road segment along the route leg’s shape.
@@ -223,6 +237,7 @@ open class RouteLeg: Codable {
                               expectedSegmentTravelTimes: expectedSegmentTravelTimes,
                               segmentSpeeds: segmentSpeeds,
                               segmentCongestionLevels: segmentCongestionLevels,
+                              segmentNumericCongestionLevels: segmentNumericCongestionLevels,
                               segmentMaximumSpeedLimits: segmentMaximumSpeedLimits)
         }
         set {
@@ -230,6 +245,7 @@ open class RouteLeg: Codable {
             expectedSegmentTravelTimes = newValue.expectedSegmentTravelTimes
             segmentSpeeds = newValue.segmentSpeeds
             segmentCongestionLevels = newValue.segmentCongestionLevels
+            segmentNumericCongestionLevels = newValue.segmentNumericCongestionLevels
             segmentMaximumSpeedLimits = newValue.segmentMaximumSpeedLimits
         }
     }
@@ -322,6 +338,7 @@ extension RouteLeg: Equatable {
             lhs.expectedSegmentTravelTimes == rhs.expectedSegmentTravelTimes &&
             lhs.segmentSpeeds == rhs.segmentSpeeds &&
             lhs.segmentCongestionLevels == rhs.segmentCongestionLevels &&
+            lhs.segmentNumericCongestionLevels == rhs.segmentNumericCongestionLevels &&
             lhs.segmentMaximumSpeedLimits == rhs.segmentMaximumSpeedLimits &&
             lhs.name == rhs.name &&
             lhs.distance == rhs.distance &&
diff --git a/Sources/MapboxDirections/RouteLegAttributes.swift b/Sources/MapboxDirections/RouteLegAttributes.swift
index 190eea72..b61cbd51 100644
--- a/Sources/MapboxDirections/RouteLegAttributes.swift
+++ b/Sources/MapboxDirections/RouteLegAttributes.swift
@@ -41,6 +41,17 @@ extension RouteLeg {
          This property is set if the `RouteOptions.attributeOptions` property contains `AttributeOptions.congestionLevel`.
          */
         public var segmentCongestionLevels: [CongestionLevel]?
+
+        /**
+         An array containing the traffic congestion level along each road segment in the route leg geometry.
+
+         Traffic data is available in [a number of countries and territories worldwide](https://docs.mapbox.com/help/how-mapbox-works/directions/#traffic-data).
+
+         You can color-code a route line according to the congestion level along each segment of the route.
+
+         This property is set if the `RouteOptions.attributeOptions` property contains `AttributeOptions.numericCongestionLevel`.
+         */
+        public var segmentNumericCongestionLevels: [NumericCongestionLevel?]?
         
         /**
          An array containing the maximum speed limit along each road segment along the route leg’s shape.
@@ -61,6 +72,7 @@ extension RouteLeg.Attributes: Codable {
         case expectedSegmentTravelTimes = "duration"
         case segmentSpeeds = "speed"
         case segmentCongestionLevels = "congestion"
+        case segmentNumericCongestionLevels = "congestion_numeric"
         case segmentMaximumSpeedLimits = "maxspeed"
     }
     
@@ -71,6 +83,7 @@ extension RouteLeg.Attributes: Codable {
         expectedSegmentTravelTimes = try container.decodeIfPresent([TimeInterval].self, forKey: .expectedSegmentTravelTimes)
         segmentSpeeds = try container.decodeIfPresent([LocationSpeed].self, forKey: .segmentSpeeds)
         segmentCongestionLevels = try container.decodeIfPresent([CongestionLevel].self, forKey: .segmentCongestionLevels)
+        segmentNumericCongestionLevels = try container.decodeIfPresent([NumericCongestionLevel?].self, forKey: .segmentNumericCongestionLevels)
         
         if let speedLimitDescriptors = try container.decodeIfPresent([SpeedLimitDescriptor].self, forKey: .segmentMaximumSpeedLimits) {
             segmentMaximumSpeedLimits = speedLimitDescriptors.map { Measurement<UnitSpeed>(speedLimitDescriptor: $0) }
@@ -86,6 +99,7 @@ extension RouteLeg.Attributes: Codable {
         try container.encodeIfPresent(expectedSegmentTravelTimes, forKey: .expectedSegmentTravelTimes)
         try container.encodeIfPresent(segmentSpeeds, forKey: .segmentSpeeds)
         try container.encodeIfPresent(segmentCongestionLevels, forKey: .segmentCongestionLevels)
+        try container.encodeIfPresent(segmentNumericCongestionLevels, forKey: .segmentNumericCongestionLevels)
         
         if let speedLimitDescriptors = segmentMaximumSpeedLimits?.map({ SpeedLimitDescriptor(speed: $0) }) {
             try container.encode(speedLimitDescriptors, forKey: .segmentMaximumSpeedLimits)
@@ -100,6 +114,7 @@ extension RouteLeg.Attributes: Codable {
             expectedSegmentTravelTimes == nil &&
             segmentSpeeds == nil &&
             segmentCongestionLevels == nil &&
+            segmentNumericCongestionLevels == nil &&
             segmentMaximumSpeedLimits == nil
     }
 }


Test patch:
diff --git a/Tests/MapboxDirectionsTests/AnnotationTests.swift b/Tests/MapboxDirectionsTests/AnnotationTests.swift
index e49bcc0f..f66c2a38 100644
--- a/Tests/MapboxDirectionsTests/AnnotationTests.swift
+++ b/Tests/MapboxDirectionsTests/AnnotationTests.swift
@@ -22,7 +22,7 @@ class AnnotationTests: XCTestCase {
             "steps": "false",
             "continue_straight": "true",
             "access_token": BogusToken,
-            "annotations": "distance,duration,speed,congestion,maxspeed"
+            "annotations": "distance,duration,speed,congestion,maxspeed,congestion_numeric"
         ]
         
         stub(condition: isHost("api.mapbox.com")
@@ -39,7 +39,7 @@ class AnnotationTests: XCTestCase {
         options.includesSteps = false
         options.includesAlternativeRoutes = false
         options.routeShapeResolution = .full
-        options.attributeOptions = [.distance, .expectedTravelTime, .speed, .congestionLevel, .maximumSpeedLimit]
+        options.attributeOptions = [.distance, .expectedTravelTime, .speed, .congestionLevel, .numericCongestionLevel, .maximumSpeedLimit]
         var route: Route?
         let task = Directions(credentials: BogusCredentials).calculate(options) { (session, disposition) in
             
@@ -75,11 +75,17 @@ class AnnotationTests: XCTestCase {
             XCTAssertEqual(leg.expectedSegmentTravelTimes?.count, 153)
             
             XCTAssertEqual(leg.segmentCongestionLevels?.count, 153)
-            XCTAssertFalse(leg.segmentCongestionLevels?.contains(.unknown) ?? true)
+            XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .unknown), 2)
             XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .low), 0)
-            XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .moderate), 21)
-            XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .heavy), 2)
+            XCTAssertEqual(leg.segmentCongestionLevels?.firstIndex(of: .moderate), 14)
+            XCTAssertFalse(leg.segmentCongestionLevels?.contains(.heavy) ?? true)
             XCTAssertFalse(leg.segmentCongestionLevels?.contains(.severe) ?? true)
+
+            XCTAssertEqual(leg.segmentNumericCongestionLevels?.count, 153)
+            XCTAssertEqual(leg.segmentNumericCongestionLevels?.firstIndex(of: nil), 2)
+            XCTAssertEqual(leg.segmentNumericCongestionLevels?.firstIndex(of: 12), 91)
+            XCTAssertEqual(leg.segmentNumericCongestionLevels?.firstIndex(of: 32), 60)
+            XCTAssertFalse(leg.segmentNumericCongestionLevels?.contains(26) ?? true)
             
             XCTAssertEqual(leg.segmentMaximumSpeedLimits?.count, 153)
             XCTAssertEqual(leg.segmentMaximumSpeedLimits?.first, Measurement(value: 48, unit: .kilometersPerHour))
diff --git a/Tests/MapboxDirectionsTests/DirectionsCredentialsTests.swift b/Tests/MapboxDirectionsTests/DirectionsCredentialsTests.swift
index f1238eea..f8255059 100644
--- a/Tests/MapboxDirectionsTests/DirectionsCredentialsTests.swift
+++ b/Tests/MapboxDirectionsTests/DirectionsCredentialsTests.swift
@@ -26,8 +26,18 @@ class DirectionsCredentialsTests: XCTestCase {
     func testSkuToken() {
         let expectedToken = "a token"
         MBXAccounts.serviceSkuToken = expectedToken
+        MBXAccounts.serviceAccessToken = Directions.shared.credentials.accessToken
         XCTAssertEqual(Directions.shared.credentials.skuToken, expectedToken)
         MBXAccounts.serviceSkuToken = nil
+        MBXAccounts.serviceAccessToken = nil
+    }
+
+    func testSkuTokenWithMismatchedAccessToken() {
+        MBXAccounts.serviceSkuToken = "a token"
+        MBXAccounts.serviceAccessToken = UUID().uuidString
+        XCTAssertEqual(Directions.shared.credentials.skuToken, nil)
+        MBXAccounts.serviceSkuToken = nil
+        MBXAccounts.serviceAccessToken = nil
     }
 #endif
 }
@@ -36,5 +46,6 @@ class DirectionsCredentialsTests: XCTestCase {
 @objc(MBXAccounts)
 final class MBXAccounts: NSObject {
     @objc static var serviceSkuToken: String?
+    @objc static var serviceAccessToken: String?
 }
 #endif
diff --git a/Tests/MapboxDirectionsTests/DirectionsTests.swift b/Tests/MapboxDirectionsTests/DirectionsTests.swift
index 7fd31fa0..81e350af 100644
--- a/Tests/MapboxDirectionsTests/DirectionsTests.swift
+++ b/Tests/MapboxDirectionsTests/DirectionsTests.swift
@@ -42,7 +42,9 @@ class DirectionsTests: XCTestCase {
         // Make sure tests run in all time zones
         NSTimeZone.default = TimeZone(secondsFromGMT: 0)!
         MBXAccounts.serviceSkuToken = skuToken
+        MBXAccounts.serviceAccessToken = BogusCredentials.accessToken
     }
+    
     override func tearDown() {
         HTTPStubs.removeAllStubs()
         super.tearDown()
diff --git a/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json b/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json
index 7458a1ca..05a36ee6 100755
--- a/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json
+++ b/Tests/MapboxDirectionsTests/Fixtures/v5/annotation.json
@@ -1,1 +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@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
+{"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","unknown","unknown","unknown","unknown","unknown","unknown","unknown","unknown","low","low","low","low","moderate","moderate","moderate","moderate","unknown","low","low","unknown","unknown","unknown","low","unknown","unknown","unknown","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","low","unknown","unknown","unknown","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","unknown","low","low","low","low","low","low","low","low","unknown","unknown","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","unknown","unknown","unknown","low","low","unknown","unknown","unknown","unknown"],"congestion_numeric":[17,17,null,null,null,null,null,null,null,null,17,17,22,22,41,41,41,41,null,0,0,null,null,null,0,null,null,null,4,4,4,4,4,4,4,4,16,16,16,16,9,9,4,4,null,null,null,6,6,6,20,20,17,37,38,38,9,9,9,9,32,32,0,0,0,0,0,0,0,0,0,0,0,0,17,17,17,1,1,0,0,0,1,0,0,0,0,0,0,0,0,12,0,0,0,0,0,0,0,null,33,33,0,0,17,17,17,17,null,null,12,12,1,1,1,1,1,1,0,0,0,0,0,27,27,12,1,1,1,0,27,27,4,4,4,0,0,0,0,0,11,11,11,11,null,null,null,0,0,null,null,null,null],"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"}
diff --git a/Tests/MapboxDirectionsTests/RouteRefreshTests.swift b/Tests/MapboxDirectionsTests/RouteRefreshTests.swift
index 76748228..cc4554c9 100644
--- a/Tests/MapboxDirectionsTests/RouteRefreshTests.swift
+++ b/Tests/MapboxDirectionsTests/RouteRefreshTests.swift
@@ -157,6 +157,9 @@ class RouteRefreshTests: XCTestCase {
                         "congestion": [
                             "severe",
                         ],
+                        "congestion_numeric": [
+                            100,
+                        ]
                     ],
                 ],
             ],
@@ -173,6 +176,7 @@ class RouteRefreshTests: XCTestCase {
             XCTAssertEqual(leg.attributes.expectedSegmentTravelTimes, [0])
             XCTAssertEqual(leg.attributes.segmentSpeeds, [0])
             XCTAssertEqual(leg.attributes.segmentCongestionLevels, [.severe])
+            XCTAssertEqual(leg.attributes.segmentNumericCongestionLevels, [100])
             XCTAssertNil(leg.attributes.segmentMaximumSpeedLimits)
         }
     }