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

Refactor annotations parameter to accept explicit values #3628

Merged
merged 12 commits into from
Feb 7, 2017

Conversation

karenzshea
Copy link
Contributor

@karenzshea karenzshea commented Jan 30, 2017

Issue

Closes #3563

Tasklist

  • Adapt annotations parsing for map matching route matching inherits from route
  • Abstract annotation insertion to steps into a function
  • update relevant Wiki pages
  • add cucumber cases (see docs/testing.md)
  • review
  • adjust for comments

| abci | abci | 1:1:10.008842:1,0:0:0:0,1:1:10.008842:0,0:0:0:0,1:1:10.010367:0 |
| trace | matchings | annotation |
| abeh | abeh | 1:10.008842:1:1:1,0:0:0:2:0,1:10.008842:1:2:0,1:10.008842:1:3:0,1:10.008842:1:4:0,0:0:0:4:0,2:19.906475:2:5:0,1:10.008842:1:6:0 |
| abci | abci | 1:10.008842:1:1:1,0:0:0:2:0,1:10.008842:1:2:0,0:0:0:2:0,1:10.010367:1:3:0 |
Copy link
Member

Choose a reason for hiding this comment

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

What are the changes here?


When I match I should get
| trace | matchings | annotation |
| ach | ach | 1,1,0,1,1,2,1 |
Copy link
Member

Choose a reason for hiding this comment

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

The annotations here are durations, right? How do I read those in the context of the node map? Why is there a duration 0 in there?

| node | id |
| a | 1 |
| b | 2 |
| c | 3 |
Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed?

annotation.values["weight"] = std::move(weights);
annotation.values["nodes"] = std::move(nodes);
annotation.values["datasources"] = std::move(datasources);
if (parameters.annotations_type & RouteParameters::AnnotationsType::Duration)
Copy link
Member

Choose a reason for hiding this comment

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

Hm either you do the bit fiddling here or what about abstracting it away? The code here could work no matter how the AnnotationsType is implemented.

nodes.values.push_back(static_cast<std::uint64_t>(node_id));
});
annotation.values["nodes"] = std::move(nodes);
}
Copy link
Member

Choose a reason for hiding this comment

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

Hm the ~100 lines above are all doing the same:

  • check if annotation x was requested
  • create json::Array for many x
  • extract step.x from RouteStep and add into array
  • add array to annotations response as "x" : [...]

I think you could abstract over this and make it extensible for the future at the same time.

What about abstracting this behind a function which takes

  • a AnnotationsType
  • a lambda taking a RouteStep and returning annotations items out of it (e.g. step.duration for AnnotationsType::Duration
  • a string describing the annotations type for the json object's key as in "key": [...] above

Example callsite:

makeAnnotation(AnnotationsType::Duration, "duration", [](auto& step){ return step.duration });

which then does all of the steps above behind the scenes and wires the parts together.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

👍 refactoring this part is already in the OP as the todo item Abstract annotation insertion to steps into a function

I was going to take a template based approach to the function, but I can consider your suggestion as possible solution as well.

@@ -67,6 +67,16 @@ struct RouteParameters : public BaseParameters
Full,
False
};
enum class AnnotationsType : int
Copy link
Member

Choose a reason for hiding this comment

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

Why the explicit : int here? Should be int by default.

Args... args_)
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
annotations_type{annotations_}, annotations{true}, geometries{geometries_},
overview{overview_}, continue_straight{continue_straight_}
Copy link
Member

Choose a reason for hiding this comment

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

Nice 👍 This should keep the API stable for the time being. We may want to ticket the removal of all those hacks for v6. I think we already have tickets for these kinds of hacks.

"nodes", engine::api::RouteParameters::AnnotationsType::Nodes)(
"distance", engine::api::RouteParameters::AnnotationsType::Distance)(
"weight", engine::api::RouteParameters::AnnotationsType::Weight)(
"datasources", engine::api::RouteParameters::AnnotationsType::Datasources);
Copy link
Member

Choose a reason for hiding this comment

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

Hm we need a mapping AnnotationsType -> string here, too. The same is needed for the response generation (as commented above). Maybe we should add this mapping already next to the AnnotationsType enum class definition. This would guarantee that we're in sync with name and enum item.

Copy link
Contributor

Choose a reason for hiding this comment

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

using karma or a map value->string? would it make sense to make json output completely in karma?

Copy link
Member

Choose a reason for hiding this comment

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

a value->string mapping would be good enough for now - our json handling is all based on a variant, not sure how karma fits in there

overview_type[ph::bind(&engine::api::RouteParameters::overview, qi::_r1) = qi::_1]);
overview_type[ph::bind(&engine::api::RouteParameters::overview, qi::_r1) = qi::_1]) |
(qi::lit("annotations=") >
annotations_type[ph::bind(add_annotation, qi::_r1, qi::_1)] % ',');
Copy link
Member

Choose a reason for hiding this comment

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

Is an empty list allowed by this? (I don't know => make a unit test)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added! to the parameters_parser unit test

RouteParameters::AnnotationsType::Weight |
RouteParameters::AnnotationsType::Nodes)),
true);
BOOST_CHECK_EQUAL(result_15->annotations, true);
Copy link
Member

Choose a reason for hiding this comment

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

Order should not matter - care to create a test expressing this?
Also edge cases such as

  • empty list
  • all possibilities
  • all possibilities including All, None and combinations

"nodes", engine::api::RouteParameters::AnnotationsType::Nodes)(
"distance", engine::api::RouteParameters::AnnotationsType::Distance)(
"weight", engine::api::RouteParameters::AnnotationsType::Weight)(
"datasources", engine::api::RouteParameters::AnnotationsType::Datasources);
Copy link
Contributor

Choose a reason for hiding this comment

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

using karma or a map value->string? would it make sense to make json output completely in karma?

enum class AnnotationsType : int
{
None = 0,
Duration = 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.

here it can start from 1<<0, but better to use explicit numerals 0x01, 0x02, ...

Copy link
Member

@daniel-j-h daniel-j-h left a comment

Choose a reason for hiding this comment

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

👍

return cb(new Error('Annotation not found in response', a_type));
got[k] = annotation[a_type];
}
});
Copy link
Member

Choose a reason for hiding this comment

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

Hm this is dup. code with above

annotations_store.values.reserve(leg.annotations.size());
std::for_each(leg.annotations.begin(),
leg.annotations.end(),
[Get, &annotations_store](const guidance::LegGeometry::Annotation &step) {
Copy link
Member

Choose a reason for hiding this comment

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

I think you can simply say const auto& step here

annotation.values["duration"] =
GetAnnotations(leg_geometry,
std::bind(&guidance::LegGeometry::Annotation::duration,
std::placeholders::_1));
Copy link
Member

Choose a reason for hiding this comment

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

Instead of bind you can simply use a lambda, which is probably easier to read and does not have all the weird quirks bind has (see https://www.youtube.com/watch?v=zt7ThwVfap0). E.g.

[](const auto& v) { return v.distance };

@@ -128,6 +132,9 @@ BOOST_AUTO_TEST_CASE(valid_route_urls)
CHECK_EQUAL_RANGE(reference_2.radiuses, result_2->radiuses);
CHECK_EQUAL_RANGE(reference_2.coordinates, result_2->coordinates);
CHECK_EQUAL_RANGE(reference_2.hints, result_2->hints);
BOOST_CHECK_EQUAL(
static_cast<bool>(result_2->annotations_type & RouteParameters::AnnotationsType::All),
true);
Copy link
Member

Choose a reason for hiding this comment

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

Hm maybe add operator== overload then? We already have | and &.

BOOST_CHECK_EQUAL(reference_17.geometries, result_17->geometries);
BOOST_CHECK_EQUAL(
static_cast<bool>(result_2->annotations_type & RouteParameters::AnnotationsType::All),
true);
Copy link
Member

Choose a reason for hiding this comment

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

Same for all of these tests, type == All

Copy link
Contributor

@oxidase oxidase left a comment

Choose a reason for hiding this comment

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

LGTM! I added some change requests, but they are minor

}
// if header matches 'a:*', parse out the values for *
// and return in that header
headers.forEach((k, v) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍 but lint complains about unused v variable

}
// if header matches 'a:*', parse out the values for *
// and return in that header
headers.forEach((k, v) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

and here v is unused

Args... args_)
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
annotations_type{annotations_}, annotations{true}, geometries{geometries_},
overview{overview_}, continue_straight{continue_straight_}
{
}

bool steps = false;
bool alternatives = false;
bool annotations = false;
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 now a duplication, better

    bool annotations() const { return annotations_type != AnnotationsType::None; };

But it is not used in code base anymore, the best would be delete it and update unit tests.

Copy link
Member

Choose a reason for hiding this comment

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

We can't do that - the annotations member attribute is in our public API - we can't change it to a function :(

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -42,6 +42,13 @@ struct RouteParametersGrammar : public BaseParametersGrammar<Iterator, Signature

RouteParametersGrammar(qi::rule<Iterator, Signature> &root_rule_) : BaseGrammar(root_rule_)
{
const auto add_annotation = [](engine::api::RouteParameters &route_parameters,
Copy link
Contributor

Choose a reason for hiding this comment

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

this lambda is not needed with operator |= and in-place phoenix bind (i added related comments)

Copy link
Contributor

Choose a reason for hiding this comment

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

due to #3628 (comment) both members must be updated, so add_annotation must remain

const boost::optional<bool> continue_straight_,
Args... args_)
: BaseParameters{std::forward<Args>(args_)...}, steps{steps_}, alternatives{alternatives_},
annotations_type{annotations_}, annotations{true}, geometries{geometries_},
Copy link
Contributor

Choose a reason for hiding this comment

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

here annotations is true even if annotations_ is None

overview_type[ph::bind(&engine::api::RouteParameters::overview, qi::_r1) = qi::_1]);
overview_type[ph::bind(&engine::api::RouteParameters::overview, qi::_r1) = qi::_1]) |
(qi::lit("annotations=") >
annotations_type[ph::bind(add_annotation, qi::_r1, qi::_1)] % ',');
Copy link
Contributor

Choose a reason for hiding this comment

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

... here in-place functor

annotations_type[ph::bind(&engine::api::RouteParameters::annotations_type, qi::_r1) |= qi::_1] % ',');

static_cast<std::underlying_type_t<RouteParameters::AnnotationsType>>(lhs) |
static_cast<std::underlying_type_t<RouteParameters::AnnotationsType>>(rhs));
}

Copy link
Contributor

Choose a reason for hiding this comment

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

... and here compound operator

inline RouteParameters::AnnotationsType& operator|=(RouteParameters::AnnotationsType& lhs,
                                                    RouteParameters::AnnotationsType rhs)
{
    return lhs = lhs | rhs;
}

engine::api::RouteParameters::AnnotationsType &route_param) {
route_parameters.annotations_type = route_parameters.annotations_type | route_param;
route_parameters.annotations = route_parameters.annotations_type != engine::api::RouteParameters::AnnotationsType::None;
return route_parameters;
Copy link
Contributor

Choose a reason for hiding this comment

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

returned value is not used and return can be removed

Copy link
Member

@TheMarex TheMarex left a comment

Choose a reason for hiding this comment

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

Code and test refactor looks great. 👍 Two thinks missing: Updating docs/http.md to reflect the new parameters and adding an entry to CHANGELOG.md.

@daniel-j-h daniel-j-h added this to the 5.6.0 milestone Feb 6, 2017
@TheMarex
Copy link
Member

TheMarex commented Feb 7, 2017

This is still missing the speed parameter. Capturing this in #3664 to be fixed in another PR.

Otherwise ready to merge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants