Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Intersections and lanes #80

Merged
merged 18 commits into from
Nov 7, 2016
Merged

Intersections and lanes #80

merged 18 commits into from
Nov 7, 2016

Conversation

bsudekum
Copy link

@bsudekum bsudekum commented Oct 17, 2016

This starts work for adding a lanes class to each step. Currently, this adds an Intersections class where the lanes class will live.

todo:

  • document
  • add lanes class

/cc @1ec5

@bsudekum
Copy link
Author

@1ec5 Added lanes, want to take a look?

@@ -0,0 +1,56 @@
import Foundation

public class Intersection: NSObject, NSSecureCoding {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use @objc to pretend the MB class prefix for Objective-C usage.

import Foundation

public class Intersection: NSObject, NSSecureCoding {
public var inIndex: Int?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an awkward place for a preposition. How about entranceIndex and exitIndex?

Also, does [Bool].Index work as the type for this property?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, to avoid confusion with the existing exitIndex property on RouteStep, how about approachIndex and outletIndex, or ingressIndex and egressIndex?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this property need to be mutable? If not, use let.

public class Intersection: NSObject, NSSecureCoding {
public var inIndex: Int?
public var outIndex: Int?
public var entry: [Bool]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of an array of Booleans, this property should be an IndexSet called outlets that contains the indices of the roads through which the user can exit the intersection.

import Foundation

public enum LaneIndicationType: Int, CustomStringConvertible {
case Left
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Swift 3, enum values start with lowercase letters.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot this is Swift 2.3. Just keep in mind that we'll need to lowercase the first letters when porting to Swift 3.


case SlightRight

case Straight
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

straightAhead for consistency with ManeuverDirection.

}
}

public class Lane: NSObject {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@objc(MBLane)

}

public class Lane: NSObject {
public var validTurn: Bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whether a lane can be used for a particular maneuver is not an intrinsic property of a lane. Remove this property and add to Intersection a usableLanes property of type IndexSet or Set<Lane>, your pick.

Copy link
Author

@bsudekum bsudekum Oct 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm unclear why this property would live on intersection. It is a specific value for a specific lane in the lanes array. ref docs

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that’s how it’s exposed in the API. But the API doesn’t use classes at all, so that’s comparing apples and oranges.

When you create a class, you make it possible for the developer to have an instance of that class all by itself. If the developer has a Lane object but not the Intersection or RouteStep object that contains it, validTurn is meaningless or misleading. It’s saying that a physical lane on a road can always or never be used for all routes that may contain the lane, which is unrealistic.

Semantically, the information you’re trying to store must live in a parallel data structure. Specifically, Intersection should have a usableLanes property that contains lanes or lane indices, and this validTurn property should be removed.

@@ -0,0 +1,83 @@
import Foundation

public enum LaneIndicationType: Int, CustomStringConvertible {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just LaneIndication is fine. But remember to use @objc here too.


public class Lane: NSObject {
public var validTurn: Bool
public var indications: [LaneIndicationType]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An array of enums won't bridge to Objective-C. We'll need a helper property that converts it to an array of NSValues containing these enums.

@@ -513,6 +513,11 @@ public class RouteStep: NSObject, NSSecureCoding {
*/
public let destinations: String?

/**
Array of interersections
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo. Also, explain that these are all the intersections along the step, starting with the one at the maneuver and ending with the one just before the next step's maneuver.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And mention the relationship between the size of this array and the next step's exitIndex property.

/**
Index into bearings/entry array.

Used to calculate the bearing just before the turn. Namely, the clockwise angle from true north to the direction of travel immediately before the maneuver/passing the intersection. Bearings are given relative to the intersection. To get the bearing in the direction of driving, the bearing has to be rotated by a value of 180. The value is not supplied for depart maneuvers.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it always 180°?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe so



/**
Objectice-c helper function for indication
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

indications won’t even appear to an Objective-C developer. You’ll have to document this property as if indications doesn’t even exist.

@@ -514,7 +514,9 @@ public class RouteStep: NSObject, NSSecureCoding {
public let destinations: String?

/**
Array of interersections
An intersection gives a full representation of any cross-way the path passes bay.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean “of any road crossing the route”?

@bsudekum
Copy link
Author

@1ec5 updated with usableLanes


Used to calculate the bearing just before the turn. Namely, the clockwise angle from true north to the direction of travel immediately before the maneuver/passing the intersection. Bearings are given relative to the intersection. To get the bearing in the direction of driving, the bearing has to be rotated by a value of 180. The value is not supplied for depart maneuvers.
*/
public let approachIndex: Int?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, Int? won’t bridge to Objective-C, so this will have to be Int instead. Set it to -1 by default (for the v4 case). Unfortunate, but this is one of the tradeoffs of supporting Objective-C.


A value of true indicates that the respective road could be entered on a valid route. false indicates that the turn onto the respective road would violate a restriction.
*/
public var entry: [Bool]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this property need to be mutable?


A value of true indicates that the respective road could be entered on a valid route. false indicates that the turn onto the respective road would violate a restriction.
*/
public var entry: [Bool]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#80 (comment)

Instead of an array of Booleans, this property should be an IndexSet called outlets that contains the indices of the roads through which the user can exit the intersection. These would be indices into the headings array. The index set would be equal in size to or smaller than the headings array.

public var lanes: [Lane]?

/**
Set of Lane objects that have a valid turn.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also mention that this is a subset of the Lane objects in the lanes property.

public var headings: [CLLocationDirection]

/**
Array of Lane objects that denote the available turn lanes at the intersection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn’t just for turn lanes.

@@ -0,0 +1,121 @@
import Foundation

@objc(MBLaneIndication)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type needs a documentation comment.

public enum LaneIndication: Int, CustomStringConvertible {

/**
An indication indicating a turn to the left.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indicates a turn to the left.

@objc(MBLane)
public class Lane: NSObject {

public let indications: [LaneIndication]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It occurs to me that we need to treat this as an option set rather than an array of enum values. After all, a lane can’t be [.Uturn, .Uturn]. Option sets are really nice in Objective-C and really nice in Swift, but unfortunately Swift option sets don’t bridge to Objective-C. So you’ll have to implement LaneIndication in Objective-C. Use MapboxGeocoder.swift’s MBPlacemarkScope option set and PlacemarkScope extension as a model.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@1ec5 are you saying use a set because the type cannot be repeated?

Copy link
Contributor

@1ec5 1ec5 Oct 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a set, but an option set (NS_OPTIONS in Objective-C, also known as a bitmask or bitfield). Not only because a type can’t be repeated, but also because using an indicationValues property (of type [NSValue]) in Objective-C would look like this:

BOOL isStraightAhead;
for (NSValue *indicationValue in lane.indicationValues) {
    MBLaneIndication laneIndication;
    [indicationValues.firstObject getValue:&laneIndication objCType:@encode(MBLaneIndication)];
    if (laneIndication == MBLaneIndicationStraightAhead) {
        isStraightAhead = YES;
        break;
    }
}
if (isStraightAhead) …

and there’s no clean way to get all the indications.

If we implement MBLaneIndication using NS_OPTIONS in Objective-C, then the Objective-C code becomes a lot simpler:

if (lane.indications & MBLaneIndicationStraightAhead) …

and the Swift code stays the same:

if lane.indications.contains(.StraightAhead) 

@@ -513,6 +513,13 @@ public class RouteStep: NSObject, NSSecureCoding {
*/
public let destinations: String?

/**
An intersection gives a full representation of any cross-way the path passes bay.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An array of intersections along the step.

Each item in the array corresponds to a cross street, starting with the intersection at the maneuver location indicated by the coordinates property and continuing with each cross street along the step.

@@ -86,6 +86,18 @@ class V5Tests: XCTestCase {
XCTAssertEqual(round(coordinate.longitude), -122)

XCTAssertEqual(leg.steps[18].name, "Sycamore Valley Road West")

let intersection = step.intersections![0]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can also say step.intersections.first!. I find that easier to type because of code completion.

Copy link
Contributor

@1ec5 1ec5 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with some feedback from previous reviews that hasn’t been addressed yet.

*/
public let approachIndex: Int?
public var approachIndex: Int = -1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don’t need to allow clients to modify these values. So you can either change this back to public let and set it to -1 in the initializer (as you seem to do anyways), or you can say public private(set) var.


If no lane information is available for an intersection, the lanes property will not be present.
If no lane information is available for an intersection, the lanes property will not be present. The first lane represents the left most lane, while the last represents the right most lane.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Is this ordering the same for countries that drive on the left?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They should line up with what's printed on the road if you were standing on it looking at the arrows

let locationArray = json["location"] as! [Double]
let location = CLLocationCoordinate2D(latitude: locationArray[0], longitude: locationArray[1])
let coords = json["location"] as! [Double]
let location = CLLocationCoordinate2D.init(geoJSON: coords)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: drop .init.

approachIndex = decoder.decodeObjectForKey("approachIndex") as? Int
outletIndex = decoder.decodeObjectForKey("outletIndex") as? Int
approachIndex = decoder.decodeObjectForKey("approachIndex") as! Int
outletIndex = decoder.decodeObjectForKey("outletIndex") as! Int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#81 correctly points out that we need to use decodeObjectOfClass(_:forKey:) to be secure. If #81 lands first, we should update the added classes in this PR to use decodeObjectOfClass(_:forKey:) too.

@@ -11,14 +11,14 @@ public class Intersection: NSObject, NSSecureCoding {

The index of the item in the headings array that corresponds to the road that the containing route step uses to approach the intersection.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mention the default value here, since Objective-C clients won’t be able to see the initializer’s implementation.

@@ -45,13 +45,13 @@ public class Intersection: NSObject, NSSecureCoding {
public var lanes: [Lane]?

/**
Set of Lane objects that have a valid turn.
Set of Lane objects that have a valid turn. This is a subset of the lanes object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: put lanes in backticks. When we generate jazzy documentation, jazzy will be able to autolink it to the lanes documentation.

@@ -45,13 +45,13 @@ public class Intersection: NSObject, NSSecureCoding {
public var lanes: [Lane]?

/**
Set of Lane objects that have a valid turn.
Set of Lane objects that have a valid turn. This is a subset of the lanes object.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes a turn “valid”?

A subset of the objects in the lanes property representing the lanes that can be used to travel from the road indicated by approachIndex to the road indicated by outletIndex.

// Indicates no turn.
StraightAhead = (1 << 7),
// Indicates no turn
Uturn = (1 << 8),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename to UTurn to match ManeuverDirection.UTurn.


typedef NS_OPTIONS(NSUInteger, MBLaneIndication) {
// Indicates a turn to the left.
Left = (1 << 1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the same order as ManeuverDirection: SharpRight, Right, SlightRight, StraightAhead, SlightLeft, Left, SharpLeft, UTurn.

type = .None
default:
return nil
public init?(descriptions: [String]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This public initializer needs a documentation comment.

case .None:
return "None"
var descriptions: [String] = []
if contains(LaneIndication.Left) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: you should be able to remove LaneIndication for brevity.

SlightRight = (1 << 6),
// Indicates no turn.
StraightAhead = (1 << 7),
// Indicates no turn
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indicates a U-turn.

StraightAhead = (1 << 7),
// Indicates no turn
Uturn = (1 << 8),
// Indicates a uturn.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No explicit turn indication.

if contains(LaneIndication.Uturn) {
descriptions.append("uturn")
}
if contains(LaneIndication.None) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between [] and ["none"] in the API response? Is it possible for the API to output either value depending on whether a way is tagged ||right or none|none|right in OSM? (The two are semantically equivalent.) If ["none"] is output regardless, then let’s ignore the "none" string and use an empty LaneIndication to represent that case. We can remove the None value from the LaneIndication enumeration.

/cc @daniel-j-h @aaronlidman

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are the only values we return:

https://github.com/Project-OSRM/osrm-backend/blob/master/docs/http.md#properties-5

||right and none|none|right are the same for OSRM and will be converted to the fixed set of return types in the spec above.

Copy link
Contributor

@1ec5 1ec5 Oct 19, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other question was whether none;right for a particular lane would turn into ["none", "right"] or ["straight", "right"] or just ["right"]. This is a tagging practice that led to some discussion in mapbox/mapping#193 and on the talk-us and tagging mailing lists.

I’m inclined to remove the None value and use [] to represent ["none"]. If ["none", right"] ever comes out of the API, we can ticket out turning that into [.StraightAhead, .Right].

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


A value of true indicates that the respective road could be entered on a valid route. false indicates that the turn onto the respective road would violate a restriction.
*/
public let entry: [Bool]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still waiting for this method to be renamed to outlets and changed to be an IndexSet.


typedef NS_OPTIONS(NSUInteger, MBLaneIndication) {
// Indicates a sharp turn to the right.
SharpRight = (1 << 4),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefix each of these values with MBLaneIndication. In Swift, MBLaneIndication will be removed automatically.

descriptions.append("slight left")
}
if contains(LaneIndication.Left) {
descriptions.append("Left")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/Left/left/

@1ec5 1ec5 self-assigned this Oct 28, 2016
Added documentation for all new public symbols. Renamed and retyped Intersection properties. Corrected coding methods. Split out LaneIndication extension into a separate Swift file. Removed the None value for LaneIndication. Updated tests.
@1ec5
Copy link
Contributor

1ec5 commented Nov 7, 2016

I’ve addressed all the feedback above and will merge this PR once most of the builds pass. Then I’ll update the swift3 branch. I’m going to let the watchOS build (the only one that actually tries to archive the framework) go red on master while we await #71.

@1ec5 1ec5 changed the title Add lanes [WIP] Intersections and lanes Nov 7, 2016
@1ec5
Copy link
Contributor

1ec5 commented Nov 7, 2016

Note that this PR does not add support for “use lane” instructions. That’s tracked in #79.

@1ec5 1ec5 merged commit 9223b88 into master Nov 7, 2016
@1ec5 1ec5 deleted the lanes branch November 7, 2016 23:59
@bsudekum
Copy link
Author

bsudekum commented Nov 8, 2016

🎉

@1ec5
Copy link
Contributor

1ec5 commented Nov 8, 2016

Merged to the swift3 branch in 67b8a95.

@1ec5 1ec5 mentioned this pull request Nov 8, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants