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

primary vs perimeter #216

Merged
merged 3 commits into from
Mar 11, 2025

Conversation

michaelkirk
Copy link
Contributor

@michaelkirk michaelkirk commented Mar 10, 2025

Fixes #176

Note: the title of #176 is a bit understated - this isn't just about "hiding a toggle" to streamline the UI. Really this is about refining the domain model. With LTNs we're concerned about the separation of busy roads (primary_road) from quieter neighbourhood roads (interior_road).

Because we were using "busy roads" as part of our severance logic (osm tag: [highway]=primary|secondary|etc.), the perimeter roads often ended up being busy roads, so they were almost synonymous, but not always. @dabreegster posted some good counter examples.

With this PR, we simply use the same severance logic to classify primary_road, and use those primary_roads in pretty much the same way we were previously using the perimeter roads.

In a lot of cases the output is pretty much identical (minus the shortcuts/calculation and styling change):

before:

Screenshot 2025-03-10 at 13 39 47

after:

Screenshot 2025-03-10 at 13 39 51

But things definitely get more interesting when you have a primary road cutting through your LTN:

before:

Screenshot 2025-03-10 at 13 35 04

after:

Screenshot 2025-03-10 at 13 35 09

before:

Screenshot 2025-03-10 at 13 42 05

after:

Screenshot 2025-03-10 at 14 41 58

One diversion I took: We'd previously talked about having border intersections only on roads that were both perimeter and primary - but looking at some actual neighbourhoods, I'm not sure that makes sense. I feel like people will be concerned with traffic from primary_roads whether that road is on the perimeter or through the middle of their LTN. Consequently, I opted to define a border intersection as any connection between a primary and interior (aka non-primary) road. Certainly it's up for discussion though!

A side benefit is we're no longer using "is_perimeter" for anything in the app. It's kind of an expensive calculation, so if we don't need it, that speeds things up

e.g.

bristol_east/build neighbourhood
                        time:   [3.9877 ms 3.9944 ms 4.0019 ms]                   
                        change: [-47.623% -47.510% -47.390%] (p = 0.00 < 0.05)
                        Performance has improved.

Note #194 is still outstanding.

The perimeter roads are not always the high traffic roads.

== PERF

This change wasn't in pursuit of performance, but it does seem to have a
positive effect. I haven't dug in, but I assume it's largely because we
no longer have to do the "Is this segment close to the perimeter?"
calculation, which was somewhat expensive.

Big improvement in `Neighbourhood::new`:

    bristol_east/build neighbourhood
			    time:   [3.9877 ms 3.9944 ms 4.0019 ms]
			    change: [-47.623% -47.510% -47.390%] (p = 0.00 < 0.05)
			    Performance has improved.

    bristol_west/build neighbourhood
			    time:   [7.7382 ms 7.7747 ms 7.8169 ms]
			    change: [-36.777% -36.434% -36.081%] (p = 0.00 < 0.05)
			    Performance has improved.

    strasbourg/build neighbourhood
			    time:   [4.1041 ms 4.1126 ms 4.1185 ms]
			    change: [-48.165% -47.997% -47.823%] (p = 0.00 < 0.05)
			    Performance has improved.

Moderate improvements in shortcuts calculation:

    shortcuts in bristol_east
                            time:   [705.70 µs 706.09 µs 706.53 µs]
                            change: [-10.140% -9.9083% -9.6860%] (p = 0.00 < 0.05)
                            Performance has improved.
    shortcuts in bristol_west
                            time:   [2.7486 ms 2.7512 ms 2.7540 ms]
                            change: [-2.1496% -1.9193% -1.6998%] (p = 0.00 < 0.05)
                            Performance has improved.
    shortcuts in strasbourg time:   [1.2332 ms 1.2350 ms 1.2368 ms]
                            change: [-34.681% -34.518% -34.340%] (p = 0.00 < 0.05)
                            Performance has improved.
),
|b| {
c.benchmark_group(neighbourhood.savefile_name)
.sample_size(neighbourhood.bench_sample_size())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Some of the Stasbourg benches were painfully slow, so I've lowered the sample size for them.

@michaelkirk
Copy link
Contributor Author

Something that's clear from the screenshots, but that I should have called out explicitly, the traffic cells for LTN's with interior primary roads are quite different from before - separating cells by the severance roads.

@michaelkirk
Copy link
Contributor Author

Something that's clear from the screenshots, but that I should have called out explicitly, the traffic cells for LTN's with interior primary roads are quite different from before - separating cells by the severance roads.

Thinking about this a little bit, that feels like a bug. I think cell color is just about communicating connectivity - you can still get between these cells by taking the primary road so they should be the same color. I'm going to revert this to a draft until I fix that.

Feel free to hold off on reviewing until then, or you can take it for a spin if you're curious.

@michaelkirk
Copy link
Contributor Author

I think cell color is just about communicating connectivity - you can still get between these cells by taking the primary road so they should be the same color.

...actually. I'm kind of spiraling on this. Every time I post a message I realize the opposite thing is true. 😆

@TFCx
Copy link

TFCx commented Mar 10, 2025

So are those interior primary roads automatically extracted from OSM data? (like primary/secondary roads tags ?). Or can I also modify them?

The cells coloring seems hierarchical. 😉 So maybe you could paint that this way:

  • cells that need to go back to the perimeter could be in different chroma
  • subcells (that live inside the same meta-cell) could be variation of saturation

@dabreegster
Copy link
Collaborator

dabreegster commented Mar 10, 2025

It may be morning before I can review this properly...

  • I will try some of the corner cases from the other issue, to see how they turn out
  • I wonder if we ought to style the primary roads to make them really stand out, like v1 did

So are those interior primary roads automatically extracted from OSM data? (like primary/secondary roads tags ?). Or can I also modify them?

Kind of relatedly... sometimes when people draw a boundary with interior perimeter roads, they want to reason about the shortcuts going through them, and place a bus gate or something like that, and see how the traffic will detour. Road classification in the UK (and I'm guessing many places) could've been done a long time ago in different policy contexts, and now there could be a desire to reconsider a "primary road" as actually just being a high street with only buses and delivery traffic.

Edit: To clarify, there could be multiple reasons somebody includes a primary (according to OSM) road through the middle of their boundary. I'm not sure if we need to behave differently.

  1. The boundary is kind of funny to draw, like the Cowgate example, and so it happens. Shortcuts on the primary road don't make sense to reason about; there's supposed to be through-traffic there.
  2. The user wants to explore designs for removing traffic from a primary road, effectively reclassifying it. We do want to show shortcuts then.

I don't think we can distinguish between the two cases without further input. I'm also not sure how common the second case is -- it's come up during some conversations about real things people are trying to do, but only a few times.

Copy link
Collaborator

@dabreegster dabreegster left a comment

Choose a reason for hiding this comment

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

Code-wise, this looks great. The perf boost is awesome.

I need to think through the implications / questions raised about border intersections for any interaction between a primary and interior road, will do that tomorrow!

// so it counts as disconnected (because it doesn't touch a border intersection), and
// thus we can't calculate shortcuts through it without those intersections.
bail!("No perimeter roads");
bail!("No primary roads");
Copy link
Collaborator

Choose a reason for hiding this comment

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

Interesting... so we can't operate on something at all if it's all residential-highway according to OSM. I don't know any use cases for that in the first place, though

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oh! I meant to call this out - I actually wasn't sure what the original comment was about, but I'm assuming it applies equally to the new primary-road based regime.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Previously, this was kind of impossible unless the user freehand drew a boundary with no roads, or not even a polygon full of roads, and we didn't see any perimeter roads almost parallel to the boundary.

Now, it's defined by primary roads, so you can't do something like this:
image
I've surfaced the error message better. We can think through if it ever makes sense to allow this -- if the user can override the primary/interior roads, then maybe so.

@@ -80,7 +80,8 @@
{...layerId("interior-roads-outlines")}
filter={["==", ["get", "kind"], "interior_road"]}
paint={{
"line-width": lineWidth($thickRoadsForShortcuts, gj.maxShortcuts, 0),
// REVIEW: the interior-roads-outline was previously not visible - was that intentional?
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just opening a comment to remind myself to look at this...

Copy link
Collaborator

Choose a reason for hiding this comment

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

Before:
image
After:
image

This was a mistake, the outlines should be visible -- I think they help contrast roads and cells. Good catch

@michaelkirk
Copy link
Contributor Author

So are those interior primary roads automatically extracted from OSM data? (like primary/secondary roads tags ?). Or can I also modify them?

They are indeed based on OSM tags.

Currently you cannot modify them in the LTN app. But I think that's a reasonable idea, and something we could add if we go forward with this direction.

@dabreegster
Copy link
Collaborator

Consequently, I opted to define a border intersection as any connection between a primary and interior (aka non-primary) road.

Ahh... imagine drawing your boundary around the entire study area. The fact that cells split on either side of these severances makes sense. You would just be showing "all" of the LTNs everywhere, on "both sides" of a severance.

@michaelkirk
Copy link
Contributor Author

michaelkirk commented Mar 10, 2025

After having it sit in the back of my mind for a couple hours, my current best guess is as @TFCx implied:

  1. We leave the severance logic how it is phrased in this PR currently (traffic cells don't cross a primary road).
  2. We add a tool to manually mark roads as primary/interior when a local expert either:
  • knows that the primary road isn't actually high traffic (or conversely if an interior road is high traffic through street)
  • is explicitly interested in seeing "shortcuts" on the primary road.

@dabreegster
Copy link
Collaborator

I think that conclusion sounds good to me too. This PR is a great self-contained step, and we can separately think through the flow for overriding this definition.

I'll try a few things out in the morning and likely merge

@dabreegster
Copy link
Collaborator

All of the examples I'm looking at are great!

image
#176 (comment) This example again looks good. I'm temporarily coloring the primary roads grey to see them better. This is a case where the user wants to take St John's Road, the primary north/south going through the middle, and treat it like a local road. We can do that with overrides next.

imagine drawing your boundary around the entire study area. The fact that cells split on either side of these severances makes sense. You would just be showing "all" of the LTNs everywhere, on "both sides" of a severance.

Going back to this thought, here's an example
image
If somebody wanted to work on several LTNs side-by-side at time, nothing stops them. It generalizes clearly from one.

Shortcuts go between two primary roads. The perimeter of the drawn boundary is not important at all. This is intuitive to me.
image

Looking again at #47, @TFCx had the right idea a year ago. It took us a while to catch up!

- Confirm interior road outlines should be visible
- Adjust shortcut mode's explanation of a shortcut
- Show the error when setting an invalid boundary
@TFCx
Copy link

TFCx commented Mar 11, 2025

Well, now i'm blushing 😊. Everything would have been faster if my english wasn't such an obstacle. 😅

Anyway, all the results are wonderful (and coloring the main arteries in grey (or at least widening the roads) is very intuitive). I can't wait to test it on Montpellier or Vendargues.

Just to be sure for the new logic, you should also test it on the non-plannar examples you had (with bridges and tunnels). :)

@dabreegster
Copy link
Collaborator

Michael, I pushed a commit with a few hopefully not-controversial fixes. I'll merge this now and file another issue to track some next steps for this. Really really awesome!

Well, now i'm blushing 😊. Everything would have been faster if my english wasn't such an obstacle.

You explained it quite well originally! I've just been really stuck on some assumptions about what the boundary should look like. Thank you for continuing to push us in the correct direction here.

Just to be sure for the new logic, you should also test it on the non-plannar examples you had (with bridges and tunnels). :)

I tried out the Bristol example and it behaves reasonably. There's a very crazy case in #123 we'll try later.

@dabreegster dabreegster merged commit 879d398 into a-b-street:main Mar 11, 2025
1 check passed
@@ -108,10 +108,7 @@
mode: "neighbourhood",
};
} catch (err) {
console.log("error when setting auto-generated neighborhood", err);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No problem with this change, but I'm curious - were you able to trigger this? (How?)

Copy link
Collaborator

Choose a reason for hiding this comment

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

image
Yes, just draw a boundary inside a larger residential area, and have no primary roads involved

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah! I think this is related to #216 (comment)

And I've triggered it by having no primary roads in my hand-drawn neighbourood:
Screenshot 2025-03-11 at 10 20 35

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.

Get rid of "Include Perimeter roads" toggle, always edit perimeter roads.
3 participants