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

control default speed assignment via config #3055

Merged
merged 21 commits into from
May 11, 2021
Merged

Conversation

kevinkreiser
Copy link
Member

@kevinkreiser kevinkreiser commented May 5, 2021

This PR adds initial support for what was proposed in #3021 of note is the change in proposed file format. We'll document that over in the other repo where the code that generates this output can be found.

At the moment this PR is still in draft because the unit tests fail. I'm having trouble getting the enhancer to think the gurka map is dense enough to be considered as urban.

@kevinkreiser kevinkreiser marked this pull request as ready for review May 6, 2021 00:46
@kevinkreiser
Copy link
Member Author

strange that the changelog CI passed... i didnt add a changelog entry yet...

Comment on lines 279 to 283
const std::string& country_state_code) {

// If we have config loaded we'll use that
if (!tables.empty() && FromConfig(directededge, density, country_state_code))
return;
Copy link
Member Author

@kevinkreiser kevinkreiser May 6, 2021

Choose a reason for hiding this comment

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

i highly recommend viewing this diff with whitespace hidden. if you dont do that it will look like i really modified a lot more than i did. what really happened here is i made this anonymous namespace function UpdateSpeed into a member function of SpeedAssigner other than indentation (and so also line wrapping) I didnt modify this function except to call the FromConfig method to first see if the config-driven speed assignment should be used.

so if you run valhalla_build_tiles as normal and don't pass it a speed config file in your regular valhalla json config, the code will hit this if above which will immediately return false and the rest of the unchange code in the UpdateSpeed function will be run as it always was.

Comment on lines 1546 to 1555
uint32_t urban_rc_speed[] = {
89, // 55 MPH - motorway
73, // 45 MPH - trunk
57, // 35 MPH - primary
49, // 30 MPH - secondary
40, // 25 MPH - tertiary
35, // 22 MPH - unclassified
30, // 20 MPH - residential
20, // 13 MPH - service/other
};
Copy link
Member Author

Choose a reason for hiding this comment

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

i did make this change because i thought it was easier to read

Comment on lines 1718 to 1720
end_node_code = admin->country_iso();
if (!end_node_code.empty())
country_state_code += admin->state_iso();
Copy link
Member Author

Choose a reason for hiding this comment

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

here i got a concatination of country and state codes for lookup in the config

@@ -1360,16 +1538,21 @@ void enhance(const boost::property_tree::ptree& pt,
// Local Graphreader
GraphReader reader(hierarchy_properties);

// Config driven speed assignment
auto speeds_config = pt.get_optional<std::string>("default_speeds");
SpeedAssigner speed_assigner(speeds_config);
Copy link
Member Author

Choose a reason for hiding this comment

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

here we potentially parse the speed config

Comment on lines 1822 to 1823
speed_assigner.UpdateSpeed(directededge, density, urban_rc_speed, infer_turn_channels,
country_state_code);
Copy link
Member Author

Choose a reason for hiding this comment

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

here we call the anonymous namespace struct to update the speed

} else {
speed = static_cast<uint32_t>((speed * kRampFactor) + 0.5f);
struct SpeedAssigner {
struct SpeedTable {
Copy link
Member Author

Choose a reason for hiding this comment

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

this object closely mirrors the json but is easier to use than a rapidjson object so we parse the json into it

Comment on lines 185 to 186
SpeedTable(cs["urban"]),
SpeedTable(cs["rural"]),
Copy link
Member Author

Choose a reason for hiding this comment

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

at the moment we have only urban and rural speed tables per each geographic region (country/state combo)

Comment on lines 192 to 196
tables.clear();
} // something else was thrown
catch (...) {
LOG_WARN("Could not load default speeds from config");
tables.clear();
Copy link
Member Author

Choose a reason for hiding this comment

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

if anything goes wrong when parsing the config its completely disabled

Copy link
Member Author

Choose a reason for hiding this comment

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

maybe this should be parsed when the tile building starts even before the enhancer stage and fail the whole build?

// Reduce speed on rough pavements. TODO - do we want to increase
// more on worse surface types?
if (directededge.surface() >= Surface::kPavedRough) {
bool FromConfig(DirectedEdge& directededge,
Copy link
Member Author

Choose a reason for hiding this comment

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

this is the main function that either uses a speed from config or aborts and relies on the previous method. anywhere where the method returns false it is signalling that it couldnt assign a speed for this edge. ive commented the sections below to make it pretty clear. if its a kind of edge we know we cant find a speed for either becuase its a special edge that we dont handle or because the geography is not in the config then we abort immediately and rely on the conventional speed setting. once it gets past that point it figures out whether it needs rural or urban speeds. finally it picks which speed to assign.

ill add a javadoc to this function

: static_cast<uint32_t>((speed * kRampFactor) + 0.5f);
} else {
speed = static_cast<uint32_t>((speed * kRampFactor) + 0.5f);
struct SpeedAssigner {
Copy link
Member Author

Choose a reason for hiding this comment

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

anyone have opinions on moving this new class out into a private header?

@kevinkreiser kevinkreiser requested a review from rzyc May 6, 2021 12:38
@gknisely
Copy link
Member

gknisely commented May 6, 2021

@kevinkreiser what are the resulting RAD diffs with this PR

@kevinkreiser
Copy link
Member Author

kevinkreiser commented May 6, 2021

@gknisely I wasn't planning on doing RAD for this but I can do a small area if you prefer. Please see my PR comments, especially this one: #3055 (comment) (i've updated the comment a bit for clarity)

The new code is disabled completely and the old code is called as normal so long as you dont pass a speeds config file in your valhalla config json.

gknisely
gknisely previously approved these changes May 7, 2021
Copy link
Member

@gknisely gknisely left a comment

Choose a reason for hiding this comment

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

If small RAD testing looked good, then merge. Thanks.

@kevinkreiser
Copy link
Member Author

@gknisely ill do a quick RAD and I want to flesh out another unit test to get more coverage of how it handles country/state specific logic

@kevinkreiser
Copy link
Member Author

@gknisely i ran RAD on 1k routes i generated from 3 corners of rhode island (providence, newport and westerly) there were 0 diffs. i'm going to add a more extensive unit test and then call it a day on this one.

rzyc
rzyc previously approved these changes May 10, 2021
Copy link
Contributor

@rzyc rzyc left a comment

Choose a reason for hiding this comment

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

lgtm

@kevinkreiser kevinkreiser dismissed stale reviews from rzyc and gknisely via a4097bf May 10, 2021 20:44
rzyc
rzyc previously approved these changes May 10, 2021

// If we have config loaded we'll use that
if (!tables.empty() && FromConfig(directededge, density, country_code, state_code)) {
directededge.set_speed_type(SpeedType::kClassified);
Copy link
Member Author

Choose a reason for hiding this comment

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

@rzyc i added this since you last reviewed realizing that it would be set improperly if the speed was a tagged speed

throw std::runtime_error("Duplicate country/state entry");
tables.emplace(std::move(code), std::array<SpeedTable, 3>{
SpeedTable(cs["rural"]),
SpeedTable(cs["suburban"]),
Copy link
Member Author

Choose a reason for hiding this comment

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

we now support a 3rd delinieation of road density

@kevinkreiser kevinkreiser merged commit 1e8b53b into master May 11, 2021
@nilsnolde
Copy link
Member

@kevinkreiser just had a little time going over this nice addition! Looks really good, thanks for that.

Do I read the code correctly, that, provided a valid JSON exists, the speed enhancer doesn't care whether the edge was tagged or not and updates its speed regardless, i.e. here:

if (!tables.empty() && FromConfig(directededge, density, country_code, state_code)) {
directededge.set_speed_type(SpeedType::kClassified);
return true;
}

If so, why would you prefer to change the tagged speed to a default speed from the JSON? My assumption would be the tagged speed is (hopefully) closer to the ground truth than a country/state-wide default speed.

@nilsnolde
Copy link
Member

Maybe it could/should be adjustable in the config by a boolean whether one wants to override tagged speeds with the JSON values?

And if tagged speeds won't be overridden by default, how about preparing a default JSON following the "official" OSM recommendations with all values being the same for urban/suburban/rural?

That also reminds me: IMO it should be possible to have missing keys or at least have some keys populated with 0 values which will signal to the algo "don't mind me, use UpdateSpeed to determine my speed". If I'm reading it right, currently it would actually determine 0 edge speed if a JSON value would be 0.

@kevinkreiser
Copy link
Member Author

kevinkreiser commented May 17, 2021

@nilsnolde

My assumption would be the tagged speed is (hopefully) closer to the ground truth than a country/state-wide default speed

I think not actually! The vast vast majority of tagged speeds are maxspeed tags. Speed limit is not a measured speed it is a suggested speed. I would agree that in areas with low traffic volumes most people either drive at or above the speed limit and in these scenarios it is a good proxy for actual travel speed. I suspect that by using a measured speed that in those areas where this is the case we'll probably not much change from what the tagged speed is. Where this will make a difference is in cities or areas with high road congestion (hopefully). I did a manual tuning and tested an area in philly and got much better times out of it. Basically places where the speed limit is aspirational 😄

However you question does resurface a thought that I was having and I think i commented somewhere. We could, instead of putting these values into DirectedEdge::speed_ we could put them into DirectedEdge::constrained_flow_. Normally that is reserved for historical traffic but the whole point of this work is if you dont have real historical traffic it gets you something better than the default speed assignments. Even better why not put all the default speed asignments into constrained_flow_ too and migrate speed_ to just be the speed limit if populated. I think that is why i marked that issue linked above as v4 because it would be a breaking tile spec change.

Regarding the other questions about when and with what to override they are all good points. I'm trying to add this support iteratively meaning I wanted to get something working and merged and come back to do further enhancements and protections which you've listed. I think its ok to not have the perfect solution just yet because the whole thing is opt-in and by default its turned off so no one is being forced into this new code path.

  1. We could add a flag that says dont override tagged speeds as is done for the default speed rules
  2. Ship the official OSM default speeds as a possible default
  3. Add fall back for unknown values to previous speed method or to adjacent geograph or something else...
  4. Handle invalid values in the json

@nilsnolde
Copy link
Member

The vast vast majority of tagged speeds are maxspeed tags

makes sense:) That does hit close to being able to distinguish maxspeed tags from other speed tags, in which case I'd prefer to keep the tagged speed rather than taking a default. Maybe those tags will be more abundant in the future by automatic data collection from all those rolling 2 ton computers..

I'll help out a little on adding those things, starting with 1. which I can already see clients whining about 😅

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.

4 participants