mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-10-26 10:09:22 +03:00
Mechanically switch to British spelling of neighbourhood.
This is yet another breaking change in the LTN proposal format
This commit is contained in:
parent
f128e3d67e
commit
4f0b150d68
@ -11,23 +11,23 @@ use widgetry::{
|
||||
};
|
||||
|
||||
use crate::filters::auto::Heuristic;
|
||||
use crate::{colors, App, Neighborhood, NeighborhoodID, Transition};
|
||||
use crate::{colors, App, Neighbourhood, NeighbourhoodID, Transition};
|
||||
|
||||
pub struct BrowseNeighborhoods {
|
||||
pub struct BrowseNeighbourhoods {
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
world: World<NeighborhoodID>,
|
||||
world: World<NeighbourhoodID>,
|
||||
draw_over_roads: ToggleZoomed,
|
||||
labels: DrawRoadLabels,
|
||||
draw_boundary_roads: ToggleZoomed,
|
||||
}
|
||||
|
||||
impl BrowseNeighborhoods {
|
||||
impl BrowseNeighbourhoods {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
map_gui::tools::update_url_map_name(app);
|
||||
|
||||
let (world, draw_over_roads) =
|
||||
ctx.loading_screen("calculate neighborhoods", |ctx, timer| {
|
||||
ctx.loading_screen("calculate neighbourhoods", |ctx, timer| {
|
||||
if &app.session.partitioning.map != app.map.get_name() {
|
||||
app.session.alt_proposals = crate::save::AltProposals::new();
|
||||
crate::clear_current_proposal(ctx, app, timer);
|
||||
@ -50,7 +50,7 @@ impl BrowseNeighborhoods {
|
||||
]),
|
||||
)
|
||||
.build(ctx);
|
||||
Box::new(BrowseNeighborhoods {
|
||||
Box::new(BrowseNeighbourhoods {
|
||||
top_panel,
|
||||
left_panel,
|
||||
world,
|
||||
@ -62,14 +62,14 @@ impl BrowseNeighborhoods {
|
||||
|
||||
pub fn button(ctx: &EventCtx, app: &App) -> Widget {
|
||||
ctx.style()
|
||||
.btn_back("Browse neighborhoods")
|
||||
.btn_back("Browse neighbourhoods")
|
||||
.hotkey(Key::Escape)
|
||||
.build_def(ctx)
|
||||
.hide(app.session.consultation.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
impl State<App> for BrowseNeighborhoods {
|
||||
impl State<App> for BrowseNeighbourhoods {
|
||||
fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
if let Some(t) = crate::components::TopPanel::event(ctx, app, &mut self.top_panel, help) {
|
||||
return t;
|
||||
@ -85,32 +85,32 @@ impl State<App> for BrowseNeighborhoods {
|
||||
));
|
||||
}
|
||||
"Automatically place filters" => {
|
||||
ctx.loading_screen("automatically filter all neighborhoods", |ctx, timer| {
|
||||
ctx.loading_screen("automatically filter all neighbourhoods", |ctx, timer| {
|
||||
timer.start_iter(
|
||||
"filter neighborhood",
|
||||
app.session.partitioning.all_neighborhoods().len(),
|
||||
"filter neighbourhood",
|
||||
app.session.partitioning.all_neighbourhoods().len(),
|
||||
);
|
||||
for id in app
|
||||
.session
|
||||
.partitioning
|
||||
.all_neighborhoods()
|
||||
.all_neighbourhoods()
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
{
|
||||
timer.next();
|
||||
let neighborhood = Neighborhood::new(ctx, app, id);
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, id);
|
||||
// Ignore errors
|
||||
let _ = app.session.heuristic.apply(ctx, app, &neighborhood, timer);
|
||||
let _ = app.session.heuristic.apply(ctx, app, &neighbourhood, timer);
|
||||
}
|
||||
});
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
return Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app));
|
||||
}
|
||||
x => {
|
||||
return crate::save::AltProposals::handle_action(
|
||||
ctx,
|
||||
app,
|
||||
crate::save::PreserveState::BrowseNeighborhoods,
|
||||
crate::save::PreserveState::BrowseNeighbourhoods,
|
||||
x,
|
||||
)
|
||||
.unwrap();
|
||||
@ -119,7 +119,7 @@ impl State<App> for BrowseNeighborhoods {
|
||||
Outcome::Changed(x) => {
|
||||
if x == "Advanced features" {
|
||||
app.opts.dev = self.left_panel.is_checked("Advanced features");
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
return Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app));
|
||||
}
|
||||
if x == "heuristic" {
|
||||
app.session.heuristic = self.left_panel.dropdown_value("heuristic");
|
||||
@ -128,7 +128,7 @@ impl State<App> for BrowseNeighborhoods {
|
||||
app.session.highlight_boundary_roads =
|
||||
self.left_panel.is_checked("highlight boundary roads");
|
||||
} else {
|
||||
app.session.draw_neighborhood_style =
|
||||
app.session.draw_neighbourhood_style =
|
||||
self.left_panel.dropdown_value("style");
|
||||
}
|
||||
|
||||
@ -168,11 +168,11 @@ impl State<App> for BrowseNeighborhoods {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_world(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> World<NeighborhoodID> {
|
||||
fn make_world(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> World<NeighbourhoodID> {
|
||||
let mut world = World::bounded(app.map.get_bounds());
|
||||
let map = &app.map;
|
||||
for (id, info) in app.session.partitioning.all_neighborhoods() {
|
||||
match app.session.draw_neighborhood_style {
|
||||
for (id, info) in app.session.partitioning.all_neighbourhoods() {
|
||||
match app.session.draw_neighbourhood_style {
|
||||
Style::SimpleColoring => {
|
||||
world
|
||||
.add(*id)
|
||||
@ -183,10 +183,10 @@ fn make_world(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> World<Neighbo
|
||||
.build(ctx);
|
||||
}
|
||||
Style::Cells => {
|
||||
// TODO The cell colors are confusing alongside the other neighborhood colors. I
|
||||
// TODO The cell colors are confusing alongside the other neighbourhood colors. I
|
||||
// tried greying out everything else, but then the view is too jumpy.
|
||||
let neighborhood = Neighborhood::new(ctx, app, *id);
|
||||
let render_cells = crate::draw_cells::RenderCells::new(map, &neighborhood);
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, *id);
|
||||
let render_cells = crate::draw_cells::RenderCells::new(map, &neighbourhood);
|
||||
let hovered_batch = render_cells.draw();
|
||||
world
|
||||
.add(*id)
|
||||
@ -197,10 +197,10 @@ fn make_world(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> World<Neighbo
|
||||
.build(ctx);
|
||||
}
|
||||
Style::Quietness => {
|
||||
let neighborhood = Neighborhood::new(ctx, app, *id);
|
||||
let shortcuts = crate::shortcuts::find_shortcuts(app, &neighborhood, timer);
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, *id);
|
||||
let shortcuts = crate::shortcuts::find_shortcuts(app, &neighbourhood, timer);
|
||||
let (quiet_streets, total_streets) =
|
||||
shortcuts.quiet_and_total_streets(&neighborhood);
|
||||
shortcuts.quiet_and_total_streets(&neighbourhood);
|
||||
let pct = if total_streets == 0 {
|
||||
0.0
|
||||
} else {
|
||||
@ -231,22 +231,22 @@ fn make_world(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> World<Neighbo
|
||||
}
|
||||
|
||||
fn draw_over_roads(ctx: &mut EventCtx, app: &App, timer: &mut Timer) -> ToggleZoomed {
|
||||
if app.session.draw_neighborhood_style != Style::Shortcuts {
|
||||
if app.session.draw_neighbourhood_style != Style::Shortcuts {
|
||||
return ToggleZoomed::empty(ctx);
|
||||
}
|
||||
|
||||
let mut count_per_road = Counter::new();
|
||||
let mut count_per_intersection = Counter::new();
|
||||
|
||||
for id in app.session.partitioning.all_neighborhoods().keys() {
|
||||
let neighborhood = Neighborhood::new(ctx, app, *id);
|
||||
let shortcuts = crate::shortcuts::find_shortcuts(app, &neighborhood, timer);
|
||||
for id in app.session.partitioning.all_neighbourhoods().keys() {
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, *id);
|
||||
let shortcuts = crate::shortcuts::find_shortcuts(app, &neighbourhood, timer);
|
||||
count_per_road.extend(shortcuts.count_per_road);
|
||||
count_per_intersection.extend(shortcuts.count_per_intersection);
|
||||
}
|
||||
|
||||
// TODO It's a bit weird to draw one heatmap covering streets in every neighborhood. The
|
||||
// shortcuts are calculated per neighborhood, but now we're showing them all together, as if
|
||||
// TODO It's a bit weird to draw one heatmap covering streets in every neighbourhood. The
|
||||
// shortcuts are calculated per neighbourhood, but now we're showing them all together, as if
|
||||
// it's the impact prediction mode using a demand model.
|
||||
let mut colorer = ColorNetwork::no_fading(app);
|
||||
colorer.ranked_roads(count_per_road, &app.cs.good_to_bad_red);
|
||||
@ -258,7 +258,7 @@ pub fn draw_boundary_roads(ctx: &EventCtx, app: &App) -> ToggleZoomed {
|
||||
let mut seen_roads = HashSet::new();
|
||||
let mut seen_borders = HashSet::new();
|
||||
let mut batch = ToggleZoomed::builder();
|
||||
for info in app.session.partitioning.all_neighborhoods().values() {
|
||||
for info in app.session.partitioning.all_neighbourhoods().values() {
|
||||
for id in &info.block.perimeter.roads {
|
||||
let r = id.road;
|
||||
if seen_roads.contains(&r) {
|
||||
@ -345,7 +345,7 @@ fn help() -> Vec<&'static str> {
|
||||
vec![
|
||||
"Basic map navigation: click and drag to pan, swipe or scroll to zoom",
|
||||
"",
|
||||
"Click a neighborhood to analyze it. You can adjust boundaries there.",
|
||||
"Click a neighbourhood to analyze it. You can adjust boundaries there.",
|
||||
]
|
||||
}
|
||||
|
||||
@ -357,11 +357,11 @@ fn advanced_panel(ctx: &EventCtx, app: &App) -> Widget {
|
||||
Line("Advanced features").small_heading().into_widget(ctx),
|
||||
Widget::col(vec![
|
||||
Widget::row(vec![
|
||||
"Draw neighborhoods:".text_widget(ctx).centered_vert(),
|
||||
"Draw neighbourhoods:".text_widget(ctx).centered_vert(),
|
||||
Widget::dropdown(
|
||||
ctx,
|
||||
"style",
|
||||
app.session.draw_neighborhood_style,
|
||||
app.session.draw_neighbourhood_style,
|
||||
vec![
|
||||
Choice::new("simple", Style::SimpleColoring),
|
||||
Choice::new("cells", Style::Cells),
|
||||
|
@ -1,9 +1,9 @@
|
||||
use widgetry::Color;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
/// Rotate through these colors for neighborhoods. Use 4-color (ehem, 6-color?) theorem to make
|
||||
/// Rotate through these colors for neighbourhoods. Use 4-color (ehem, 6-color?) theorem to make
|
||||
/// adjacent things different
|
||||
pub static ref NEIGHBORHOODS: [Color; 6] = [
|
||||
pub static ref NEIGHBOURHOODS: [Color; 6] = [
|
||||
Color::BLUE.alpha(0.3),
|
||||
Color::YELLOW.alpha(0.3),
|
||||
Color::GREEN.alpha(0.3),
|
||||
|
@ -5,7 +5,7 @@ use map_model::{IntersectionID, PathConstraints, Perimeter};
|
||||
use widgetry::tools::PolyLineLasso;
|
||||
use widgetry::{DrawBaselayer, EventCtx, GfxCtx, Key, Line, ScreenPt, State, Text, Widget};
|
||||
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighborhood, Transition};
|
||||
use crate::{after_edit, App, DiagonalFilter, Neighbourhood, Transition};
|
||||
|
||||
pub struct FreehandFilters {
|
||||
lasso: PolyLineLasso,
|
||||
@ -18,13 +18,13 @@ pub struct FreehandFilters {
|
||||
impl FreehandFilters {
|
||||
pub fn new_state(
|
||||
ctx: &EventCtx,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
instructions_at: ScreenPt,
|
||||
) -> Box<dyn State<App>> {
|
||||
Box::new(Self {
|
||||
lasso: PolyLineLasso::new(),
|
||||
perimeter: neighborhood.orig_perimeter.clone(),
|
||||
interior_intersections: neighborhood.interior_intersections.clone(),
|
||||
perimeter: neighbourhood.orig_perimeter.clone(),
|
||||
interior_intersections: neighbourhood.interior_intersections.clone(),
|
||||
instructions_at,
|
||||
instructions: Text::from_all(vec![
|
||||
Line("Click and drag").fg(ctx.style().text_hotkey_color),
|
||||
|
@ -5,7 +5,7 @@ use widgetry::{
|
||||
VerticalAlignment, Widget,
|
||||
};
|
||||
|
||||
use crate::{App, BrowseNeighborhoods, Transition};
|
||||
use crate::{App, BrowseNeighbourhoods, Transition};
|
||||
|
||||
pub struct TopPanel;
|
||||
|
||||
@ -19,7 +19,7 @@ impl TopPanel {
|
||||
Line(if consultation {
|
||||
"East Bristol Liveable Neighbourhood"
|
||||
} else {
|
||||
"Low traffic neighborhoods"
|
||||
"Low traffic neighbourhoods"
|
||||
})
|
||||
.small_heading()
|
||||
.into_widget(ctx)
|
||||
@ -81,7 +81,7 @@ impl TopPanel {
|
||||
ctx,
|
||||
app,
|
||||
map_gui::tools::Executable::LTN,
|
||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
||||
Box::new(|ctx, app, _| BrowseNeighbourhoods::new_state(ctx, app)),
|
||||
),
|
||||
]))
|
||||
} else {
|
||||
@ -92,7 +92,7 @@ impl TopPanel {
|
||||
ctx,
|
||||
app,
|
||||
Box::new(|ctx, app| {
|
||||
Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
|
||||
Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app))
|
||||
}),
|
||||
))),
|
||||
"search" => Some(Transition::Push(map_gui::tools::Navigator::new_state(
|
||||
|
@ -7,29 +7,29 @@ use widgetry::{
|
||||
DrawBaselayer, EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, TextExt, Toggle, Widget,
|
||||
};
|
||||
|
||||
use crate::edit::{EditNeighborhood, Tab};
|
||||
use crate::edit::{EditNeighbourhood, Tab};
|
||||
use crate::filters::auto::Heuristic;
|
||||
use crate::shortcuts::find_shortcuts;
|
||||
use crate::{colors, App, Neighborhood, NeighborhoodID, Transition};
|
||||
use crate::{colors, App, Neighbourhood, NeighbourhoodID, Transition};
|
||||
|
||||
pub struct Viewer {
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
neighborhood: Neighborhood,
|
||||
neighbourhood: Neighbourhood,
|
||||
draw_top_layer: ToggleZoomed,
|
||||
edit: EditNeighborhood,
|
||||
edit: EditNeighbourhood,
|
||||
}
|
||||
|
||||
impl Viewer {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighborhoodID) -> Box<dyn State<App>> {
|
||||
let neighborhood = Neighborhood::new(ctx, app, id);
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighbourhoodID) -> Box<dyn State<App>> {
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, id);
|
||||
|
||||
let mut viewer = Viewer {
|
||||
top_panel: crate::components::TopPanel::panel(ctx, app),
|
||||
left_panel: Panel::empty(ctx),
|
||||
neighborhood,
|
||||
neighbourhood,
|
||||
draw_top_layer: ToggleZoomed::empty(ctx),
|
||||
edit: EditNeighborhood::temporary(),
|
||||
edit: EditNeighbourhood::temporary(),
|
||||
};
|
||||
viewer.update(ctx, app);
|
||||
Box::new(viewer)
|
||||
@ -37,7 +37,7 @@ impl Viewer {
|
||||
|
||||
fn update(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
let disconnected_cells = self
|
||||
.neighborhood
|
||||
.neighbourhood
|
||||
.cells
|
||||
.iter()
|
||||
.filter(|c| c.is_disconnected())
|
||||
@ -57,10 +57,10 @@ impl Viewer {
|
||||
&self.top_panel,
|
||||
Widget::col(vec![
|
||||
format!(
|
||||
"Neighborhood area: {}",
|
||||
"Neighbourhood area: {}",
|
||||
app.session
|
||||
.partitioning
|
||||
.neighborhood_area_km2(self.neighborhood.id)
|
||||
.neighbourhood_area_km2(self.neighbourhood.id)
|
||||
)
|
||||
.text_widget(ctx),
|
||||
warning.text_widget(ctx),
|
||||
@ -69,7 +69,7 @@ impl Viewer {
|
||||
)
|
||||
.build(ctx);
|
||||
|
||||
let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood);
|
||||
let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighbourhood);
|
||||
self.edit = edit;
|
||||
self.draw_top_layer = draw_top_layer;
|
||||
}
|
||||
@ -83,13 +83,17 @@ impl State<App> for Viewer {
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => {
|
||||
if x == "Automatically place filters" {
|
||||
match ctx.loading_screen("automatically filter a neighborhood", |ctx, timer| {
|
||||
app.session
|
||||
.heuristic
|
||||
.apply(ctx, app, &self.neighborhood, timer)
|
||||
}) {
|
||||
match ctx.loading_screen(
|
||||
"automatically filter a neighbourhood",
|
||||
|ctx, timer| {
|
||||
app.session
|
||||
.heuristic
|
||||
.apply(ctx, app, &self.neighbourhood, timer)
|
||||
},
|
||||
) {
|
||||
Ok(()) => {
|
||||
self.neighborhood = Neighborhood::new(ctx, app, self.neighborhood.id);
|
||||
self.neighbourhood =
|
||||
Neighbourhood::new(ctx, app, self.neighbourhood.id);
|
||||
self.update(ctx, app);
|
||||
return Transition::Keep;
|
||||
}
|
||||
@ -106,14 +110,14 @@ impl State<App> for Viewer {
|
||||
crate::customize_boundary::CustomizeBoundary::new_state(
|
||||
ctx,
|
||||
app,
|
||||
self.neighborhood.id,
|
||||
self.neighbourhood.id,
|
||||
),
|
||||
);
|
||||
} else if let Some(t) = self.edit.handle_panel_action(
|
||||
ctx,
|
||||
app,
|
||||
x.as_ref(),
|
||||
&self.neighborhood,
|
||||
&self.neighbourhood,
|
||||
&self.left_panel,
|
||||
) {
|
||||
return t;
|
||||
@ -125,7 +129,7 @@ impl State<App> for Viewer {
|
||||
crate::save::PreserveState::Connectivity(
|
||||
app.session
|
||||
.partitioning
|
||||
.all_blocks_in_neighborhood(self.neighborhood.id),
|
||||
.all_blocks_in_neighbourhood(self.neighbourhood.id),
|
||||
),
|
||||
&x,
|
||||
)
|
||||
@ -142,7 +146,7 @@ impl State<App> for Viewer {
|
||||
app.session.heuristic = self.left_panel.dropdown_value("heuristic");
|
||||
|
||||
if x != "heuristic" {
|
||||
let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighborhood);
|
||||
let (edit, draw_top_layer) = setup_editing(ctx, app, &self.neighbourhood);
|
||||
self.edit = edit;
|
||||
self.draw_top_layer = draw_top_layer;
|
||||
}
|
||||
@ -151,7 +155,7 @@ impl State<App> for Viewer {
|
||||
}
|
||||
|
||||
if self.edit.event(ctx, app) {
|
||||
self.neighborhood = Neighborhood::new(ctx, app, self.neighborhood.id);
|
||||
self.neighbourhood = Neighbourhood::new(ctx, app, self.neighbourhood.id);
|
||||
self.update(ctx, app);
|
||||
}
|
||||
|
||||
@ -164,7 +168,7 @@ impl State<App> for Viewer {
|
||||
|
||||
fn draw(&self, g: &mut GfxCtx, app: &App) {
|
||||
crate::draw_with_layering(g, app, |g| self.edit.world.draw(g));
|
||||
g.redraw(&self.neighborhood.fade_irrelevant);
|
||||
g.redraw(&self.neighbourhood.fade_irrelevant);
|
||||
self.draw_top_layer.draw(g);
|
||||
|
||||
self.top_panel.draw(g);
|
||||
@ -174,32 +178,32 @@ impl State<App> for Viewer {
|
||||
// same might be nice. And we should seed the quadtree with the locations of filters and
|
||||
// arrows, possibly.
|
||||
if g.canvas.is_unzoomed() {
|
||||
self.neighborhood.labels.draw(g, app);
|
||||
self.neighbourhood.labels.draw(g, app);
|
||||
}
|
||||
}
|
||||
|
||||
fn recreate(&mut self, ctx: &mut EventCtx, app: &mut App) -> Box<dyn State<App>> {
|
||||
Self::new_state(ctx, app, self.neighborhood.id)
|
||||
Self::new_state(ctx, app, self.neighbourhood.id)
|
||||
}
|
||||
}
|
||||
|
||||
fn setup_editing(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
neighborhood: &Neighborhood,
|
||||
) -> (EditNeighborhood, ToggleZoomed) {
|
||||
neighbourhood: &Neighbourhood,
|
||||
) -> (EditNeighbourhood, ToggleZoomed) {
|
||||
let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| {
|
||||
find_shortcuts(app, neighborhood, timer)
|
||||
find_shortcuts(app, neighbourhood, timer)
|
||||
});
|
||||
|
||||
let mut edit = EditNeighborhood::new(ctx, app, neighborhood, &shortcuts);
|
||||
let mut edit = EditNeighbourhood::new(ctx, app, neighbourhood, &shortcuts);
|
||||
let map = &app.map;
|
||||
|
||||
// The world is drawn in between areas and roads, but some things need to be drawn on top of
|
||||
// roads
|
||||
let mut draw_top_layer = ToggleZoomed::builder();
|
||||
|
||||
let render_cells = crate::draw_cells::RenderCells::new(map, neighborhood);
|
||||
let render_cells = crate::draw_cells::RenderCells::new(map, neighbourhood);
|
||||
if app.session.draw_cells_as_areas {
|
||||
edit.world.draw_master_batch(ctx, render_cells.draw());
|
||||
|
||||
@ -213,7 +217,7 @@ fn setup_editing(
|
||||
|
||||
draw_top_layer.append(colorer.draw);
|
||||
} else {
|
||||
for (idx, cell) in neighborhood.cells.iter().enumerate() {
|
||||
for (idx, cell) in neighbourhood.cells.iter().enumerate() {
|
||||
let color = render_cells.colors[idx].alpha(0.9);
|
||||
for (r, interval) in &cell.roads {
|
||||
let road = map.get_r(*r);
|
||||
@ -233,23 +237,23 @@ fn setup_editing(
|
||||
}
|
||||
|
||||
// Draw the borders of each cell
|
||||
for (idx, cell) in neighborhood.cells.iter().enumerate() {
|
||||
for (idx, cell) in neighbourhood.cells.iter().enumerate() {
|
||||
let color = render_cells.colors[idx];
|
||||
for i in &cell.borders {
|
||||
// Most borders only have one road in the interior of the neighborhood. Draw an arrow
|
||||
// Most borders only have one road in the interior of the neighbourhood. Draw an arrow
|
||||
// for each of those. If there happen to be multiple interior roads for one border, the
|
||||
// arrows will overlap each other -- but that happens anyway with borders close
|
||||
// together at certain angles.
|
||||
for r in cell.roads.keys() {
|
||||
let road = map.get_r(*r);
|
||||
// Design choice: when we have a filter right at the entrance of a neighborhood, it
|
||||
// Design choice: when we have a filter right at the entrance of a neighbourhood, it
|
||||
// creates its own little cell allowing access to just the very beginning of the
|
||||
// road. Let's not draw anything for that.
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the angle pointing into the neighborhood
|
||||
// Find the angle pointing into the neighbourhood
|
||||
let angle_in = if road.src_i == *i {
|
||||
road.center_pts.first_line().angle()
|
||||
} else if road.dst_i == *i {
|
||||
@ -284,11 +288,11 @@ fn setup_editing(
|
||||
}
|
||||
|
||||
// Draw one-way arrows
|
||||
for r in neighborhood
|
||||
for r in neighbourhood
|
||||
.orig_perimeter
|
||||
.interior
|
||||
.iter()
|
||||
.chain(neighborhood.orig_perimeter.roads.iter().map(|id| &id.road))
|
||||
.chain(neighbourhood.orig_perimeter.roads.iter().map(|id| &id.road))
|
||||
{
|
||||
let road = map.get_r(*r);
|
||||
if let Some(dir) = road.oneway_for_driving() {
|
||||
@ -321,7 +325,7 @@ fn setup_editing(
|
||||
|
||||
fn help() -> Vec<&'static str> {
|
||||
vec![
|
||||
"The colored cells show where it's possible to drive without leaving the neighborhood.",
|
||||
"The colored cells show where it's possible to drive without leaving the neighbourhood.",
|
||||
"",
|
||||
"The darker red roads have more predicted shortcutting traffic.",
|
||||
"",
|
||||
|
@ -5,20 +5,20 @@ use widgetry::{
|
||||
Widget,
|
||||
};
|
||||
|
||||
use crate::{App, NeighborhoodID, Transition};
|
||||
use crate::{App, NeighbourhoodID, Transition};
|
||||
|
||||
pub struct CustomizeBoundary {
|
||||
panel: Panel,
|
||||
edit: EditPolygon,
|
||||
id: NeighborhoodID,
|
||||
id: NeighbourhoodID,
|
||||
}
|
||||
|
||||
impl CustomizeBoundary {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighborhoodID) -> Box<dyn State<App>> {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighbourhoodID) -> Box<dyn State<App>> {
|
||||
let points = app
|
||||
.session
|
||||
.partitioning
|
||||
.neighborhood_boundary_polygon(app, id)
|
||||
.neighbourhood_boundary_polygon(app, id)
|
||||
.into_points();
|
||||
Box::new(Self {
|
||||
id,
|
||||
@ -53,7 +53,7 @@ impl State<App> for CustomizeBoundary {
|
||||
if let Ok(ring) = Ring::new(pts) {
|
||||
app.session
|
||||
.partitioning
|
||||
.override_neighborhood_boundary_polygon(self.id, ring.into_polygon());
|
||||
.override_neighbourhood_boundary_polygon(self.id, ring.into_polygon());
|
||||
return Transition::Multi(vec![Transition::Pop, Transition::Recreate]);
|
||||
}
|
||||
// Silently stay here so the user can try to fix
|
||||
|
@ -5,7 +5,7 @@ use map_gui::tools::Grid;
|
||||
use map_model::Map;
|
||||
use widgetry::{Color, GeomBatch};
|
||||
|
||||
use crate::{colors, Neighborhood};
|
||||
use crate::{colors, Neighbourhood};
|
||||
|
||||
const RESOLUTION_M: f64 = 10.0;
|
||||
|
||||
@ -16,22 +16,22 @@ pub struct RenderCells {
|
||||
}
|
||||
|
||||
struct RenderCellsBuilder {
|
||||
/// The grid only covers the boundary polygon of the neighborhood. The values are cell indices,
|
||||
/// and `Some(num_cells)` marks the boundary of the neighborhood.
|
||||
/// The grid only covers the boundary polygon of the neighbourhood. The values are cell indices,
|
||||
/// and `Some(num_cells)` marks the boundary of the neighbourhood.
|
||||
grid: Grid<Option<usize>>,
|
||||
colors: Vec<Color>,
|
||||
/// Bounds of the neighborhood boundary polygon
|
||||
/// Bounds of the neighbourhood boundary polygon
|
||||
bounds: Bounds,
|
||||
|
||||
boundary_polygon: Polygon,
|
||||
}
|
||||
|
||||
impl RenderCells {
|
||||
/// Partition a neighborhood's boundary polygon based on the cells. This discretizes space into
|
||||
/// Partition a neighbourhood's boundary polygon based on the cells. This discretizes space into
|
||||
/// a grid, and then extracts a polygon from the raster. The results don't look perfect, but
|
||||
/// it's fast.
|
||||
pub fn new(map: &Map, neighborhood: &Neighborhood) -> RenderCells {
|
||||
RenderCellsBuilder::new(map, neighborhood).finalize()
|
||||
pub fn new(map: &Map, neighbourhood: &Neighbourhood) -> RenderCells {
|
||||
RenderCellsBuilder::new(map, neighbourhood).finalize()
|
||||
}
|
||||
|
||||
pub fn draw(&self) -> GeomBatch {
|
||||
@ -55,8 +55,8 @@ impl RenderCells {
|
||||
}
|
||||
|
||||
impl RenderCellsBuilder {
|
||||
fn new(map: &Map, neighborhood: &Neighborhood) -> RenderCellsBuilder {
|
||||
let boundary_polygon = neighborhood
|
||||
fn new(map: &Map, neighbourhood: &Neighbourhood) -> RenderCellsBuilder {
|
||||
let boundary_polygon = neighbourhood
|
||||
.orig_perimeter
|
||||
.clone()
|
||||
.to_block(map)
|
||||
@ -73,7 +73,7 @@ impl RenderCellsBuilder {
|
||||
|
||||
// Initially fill out the grid based on the roads in each cell
|
||||
let mut warn_leak = true;
|
||||
for (cell_idx, cell) in neighborhood.cells.iter().enumerate() {
|
||||
for (cell_idx, cell) in neighbourhood.cells.iter().enumerate() {
|
||||
for (r, interval) in &cell.roads {
|
||||
let road = map.get_r(*r);
|
||||
// Some roads with a filter are _very_ short, and this fails. The connecting roads
|
||||
@ -91,18 +91,18 @@ impl RenderCellsBuilder {
|
||||
((pt.x() - bounds.min_x) / RESOLUTION_M) as usize,
|
||||
((pt.y() - bounds.min_y) / RESOLUTION_M) as usize,
|
||||
);
|
||||
// Due to tunnels/bridges, sometimes a road belongs to a neighborhood, but
|
||||
// leaks outside the neighborhood's boundary. Avoid crashing. The real fix
|
||||
// Due to tunnels/bridges, sometimes a road belongs to a neighbourhood, but
|
||||
// leaks outside the neighbourhood's boundary. Avoid crashing. The real fix
|
||||
// is to better define boundaries in the face of z-order changes.
|
||||
//
|
||||
// Example is https://www.openstreetmap.org/way/87298633
|
||||
if grid_idx >= grid.data.len() {
|
||||
if warn_leak {
|
||||
warn!(
|
||||
"{} leaks outside its neighborhood's boundary polygon, near {}",
|
||||
"{} leaks outside its neighbourhood's boundary polygon, near {}",
|
||||
road.id, pt
|
||||
);
|
||||
// In some neighborhoods, there are so many warnings that logging
|
||||
// In some neighbourhoods, there are so many warnings that logging
|
||||
// causes noticeable slowdown!
|
||||
warn_leak = false;
|
||||
}
|
||||
@ -119,7 +119,7 @@ impl RenderCellsBuilder {
|
||||
// Also mark the boundary polygon, so we can prevent the diffusion from "leaking" outside
|
||||
// the area. The grid covers the rectangular bounds of the polygon. Rather than make an
|
||||
// enum with 3 cases, just assign a new index to mean "boundary."
|
||||
let boundary_marker = neighborhood.cells.len();
|
||||
let boundary_marker = neighbourhood.cells.len();
|
||||
for (pt, _) in
|
||||
geom::PolyLine::unchecked_new(boundary_polygon.clone().into_ring().into_points())
|
||||
.step_along(Distance::meters(RESOLUTION_M / 2.0), Distance::ZERO)
|
||||
@ -134,10 +134,10 @@ impl RenderCellsBuilder {
|
||||
}
|
||||
|
||||
let adjacencies = diffusion(&mut grid, boundary_marker);
|
||||
let mut cell_colors = color_cells(neighborhood.cells.len(), adjacencies);
|
||||
let mut cell_colors = color_cells(neighbourhood.cells.len(), adjacencies);
|
||||
|
||||
// Color some special cells
|
||||
for (idx, cell) in neighborhood.cells.iter().enumerate() {
|
||||
for (idx, cell) in neighbourhood.cells.iter().enumerate() {
|
||||
if cell.is_disconnected() {
|
||||
cell_colors[idx] = colors::DISCONNECTED_CELL;
|
||||
}
|
||||
@ -202,7 +202,7 @@ impl RenderCellsBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
// Sometimes one cell "leaks" out of the neighborhood boundary. Not sure why. But we
|
||||
// Sometimes one cell "leaks" out of the neighbourhood boundary. Not sure why. But we
|
||||
// can just clip the result.
|
||||
let mut clipped = Vec::new();
|
||||
for p in cell_polygons {
|
||||
|
@ -6,7 +6,7 @@ use widgetry::{lctrl, EventCtx, Image, Key, Line, Text, TextExt, Widget};
|
||||
|
||||
use super::Obj;
|
||||
use crate::shortcuts::Shortcuts;
|
||||
use crate::{after_edit, colors, App, DiagonalFilter, Neighborhood};
|
||||
use crate::{after_edit, colors, App, DiagonalFilter, Neighbourhood};
|
||||
|
||||
pub fn widget(ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
Widget::col(vec![
|
||||
@ -44,13 +44,13 @@ pub fn widget(ctx: &mut EventCtx, app: &App) -> Widget {
|
||||
pub fn make_world(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
shortcuts: &Shortcuts,
|
||||
) -> World<Obj> {
|
||||
let map = &app.map;
|
||||
let mut world = World::bounded(map.get_bounds());
|
||||
|
||||
for r in &neighborhood.orig_perimeter.interior {
|
||||
for r in &neighbourhood.orig_perimeter.interior {
|
||||
let road = map.get_r(*r);
|
||||
world
|
||||
.add(Obj::InteriorRoad(*r))
|
||||
@ -67,7 +67,7 @@ pub fn make_world(
|
||||
.build(ctx);
|
||||
}
|
||||
|
||||
for i in &neighborhood.interior_intersections {
|
||||
for i in &neighbourhood.interior_intersections {
|
||||
world
|
||||
.add(Obj::InteriorIntersection(*i))
|
||||
.hitbox(map.get_i(*i).polygon.clone())
|
||||
|
@ -6,7 +6,7 @@ use widgetry::mapspace::{ObjectID, World};
|
||||
use widgetry::{EventCtx, Key, Line, Panel, PanelBuilder, Widget, DEFAULT_CORNER_RADIUS};
|
||||
|
||||
use crate::shortcuts::Shortcuts;
|
||||
use crate::{after_edit, App, BrowseNeighborhoods, Neighborhood, Transition};
|
||||
use crate::{after_edit, App, BrowseNeighbourhoods, Neighbourhood, Transition};
|
||||
|
||||
// TODO This is only used for styling now
|
||||
#[derive(PartialEq)]
|
||||
@ -60,7 +60,7 @@ impl Tab {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EditNeighborhood {
|
||||
pub struct EditNeighbourhood {
|
||||
// Only pub for drawing
|
||||
pub world: World<Obj>,
|
||||
}
|
||||
@ -72,7 +72,7 @@ pub enum Obj {
|
||||
}
|
||||
impl ObjectID for Obj {}
|
||||
|
||||
impl EditNeighborhood {
|
||||
impl EditNeighbourhood {
|
||||
pub fn temporary() -> Self {
|
||||
Self {
|
||||
world: World::unbounded(),
|
||||
@ -82,14 +82,14 @@ impl EditNeighborhood {
|
||||
pub fn new(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
shortcuts: &Shortcuts,
|
||||
) -> Self {
|
||||
Self {
|
||||
world: if app.session.edit_filters {
|
||||
filters::make_world(ctx, app, neighborhood, shortcuts)
|
||||
filters::make_world(ctx, app, neighbourhood, shortcuts)
|
||||
} else {
|
||||
one_ways::make_world(ctx, app, neighborhood)
|
||||
one_ways::make_world(ctx, app, neighbourhood)
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -104,8 +104,8 @@ impl EditNeighborhood {
|
||||
) -> PanelBuilder {
|
||||
let contents = Widget::col(vec![
|
||||
app.session.alt_proposals.to_widget(ctx, app),
|
||||
BrowseNeighborhoods::button(ctx, app),
|
||||
Line("Editing neighborhood")
|
||||
BrowseNeighbourhoods::button(ctx, app),
|
||||
Line("Editing neighbourhood")
|
||||
.small_heading()
|
||||
.into_widget(ctx),
|
||||
edit_mode(ctx, app.session.edit_filters),
|
||||
@ -122,7 +122,7 @@ impl EditNeighborhood {
|
||||
crate::components::LeftPanel::builder(ctx, top_panel, contents)
|
||||
}
|
||||
|
||||
/// If true, the neighborhood has changed and the caller should recalculate stuff, including
|
||||
/// If true, the neighbourhood has changed and the caller should recalculate stuff, including
|
||||
/// the panel
|
||||
pub fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> bool {
|
||||
let outcome = self.world.event(ctx);
|
||||
@ -138,14 +138,14 @@ impl EditNeighborhood {
|
||||
ctx: &mut EventCtx,
|
||||
app: &mut App,
|
||||
action: &str,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
panel: &Panel,
|
||||
) -> Option<Transition> {
|
||||
let id = neighborhood.id;
|
||||
let id = neighbourhood.id;
|
||||
match action {
|
||||
"Browse neighborhoods" => {
|
||||
"Browse neighbourhoods" => {
|
||||
// Recalculate the state to redraw any changed filters
|
||||
Some(Transition::Replace(BrowseNeighborhoods::new_state(
|
||||
Some(Transition::Replace(BrowseNeighbourhoods::new_state(
|
||||
ctx, app,
|
||||
)))
|
||||
}
|
||||
@ -162,7 +162,7 @@ impl EditNeighborhood {
|
||||
"Create filters along a shape" => Some(Transition::Push(
|
||||
crate::components::FreehandFilters::new_state(
|
||||
ctx,
|
||||
neighborhood,
|
||||
neighbourhood,
|
||||
panel.center_of("Create filters along a shape"),
|
||||
),
|
||||
)),
|
||||
|
@ -5,7 +5,7 @@ use widgetry::mapspace::{World, WorldOutcome};
|
||||
use widgetry::{EventCtx, Image, Text, TextExt, Widget};
|
||||
|
||||
use super::Obj;
|
||||
use crate::{colors, App, Neighborhood};
|
||||
use crate::{colors, App, Neighbourhood};
|
||||
|
||||
pub fn widget(ctx: &mut EventCtx) -> Widget {
|
||||
Widget::col(vec![
|
||||
@ -19,11 +19,11 @@ pub fn widget(ctx: &mut EventCtx) -> Widget {
|
||||
])
|
||||
}
|
||||
|
||||
pub fn make_world(ctx: &mut EventCtx, app: &App, neighborhood: &Neighborhood) -> World<Obj> {
|
||||
pub fn make_world(ctx: &mut EventCtx, app: &App, neighbourhood: &Neighbourhood) -> World<Obj> {
|
||||
let map = &app.map;
|
||||
let mut world = World::bounded(map.get_bounds());
|
||||
|
||||
for r in &neighborhood.orig_perimeter.interior {
|
||||
for r in &neighbourhood.orig_perimeter.interior {
|
||||
let road = map.get_r(*r);
|
||||
if !PathConstraints::Car.can_use_road(road, map) {
|
||||
continue;
|
||||
|
@ -3,7 +3,7 @@ use anyhow::Result;
|
||||
use geom::{PolyLine, Pt2D};
|
||||
use widgetry::EventCtx;
|
||||
|
||||
use crate::{App, Neighborhood};
|
||||
use crate::{App, Neighbourhood};
|
||||
|
||||
/// Returns the path where the file was written
|
||||
pub fn write_geojson_file(ctx: &EventCtx, app: &App) -> Result<String> {
|
||||
@ -19,8 +19,8 @@ fn geojson_string(ctx: &EventCtx, app: &App) -> Result<String> {
|
||||
let map = &app.map;
|
||||
let mut features = Vec::new();
|
||||
|
||||
// All neighborhood boundaries
|
||||
for (id, info) in app.session.partitioning.all_neighborhoods() {
|
||||
// All neighbourhood boundaries
|
||||
for (id, info) in app.session.partitioning.all_neighbourhoods() {
|
||||
let mut feature = Feature {
|
||||
bbox: None,
|
||||
geometry: Some(info.block.polygon.to_geojson(None)),
|
||||
@ -28,15 +28,15 @@ fn geojson_string(ctx: &EventCtx, app: &App) -> Result<String> {
|
||||
properties: None,
|
||||
foreign_members: None,
|
||||
};
|
||||
feature.set_property("type", "neighborhood");
|
||||
feature.set_property("type", "neighbourhood");
|
||||
feature.set_property("fill", info.color.as_hex());
|
||||
// Cells should cover these up
|
||||
feature.set_property("fill-opacity", 0.0);
|
||||
features.push(feature);
|
||||
|
||||
// Cells per neighborhood
|
||||
// Cells per neighbourhood
|
||||
let render_cells =
|
||||
crate::draw_cells::RenderCells::new(map, &Neighborhood::new(ctx, app, *id));
|
||||
crate::draw_cells::RenderCells::new(map, &Neighbourhood::new(ctx, app, *id));
|
||||
for (idx, multipolygon) in render_cells.to_multipolygons().into_iter().enumerate() {
|
||||
let mut feature = Feature {
|
||||
bbox: None,
|
||||
|
@ -1,4 +1,4 @@
|
||||
//! Experiments to make a neighborhood be low-traffic by automatically placing filters to prevent
|
||||
//! Experiments to make a neighbourhood be low-traffic by automatically placing filters to prevent
|
||||
//! all shortcuts.
|
||||
|
||||
use anyhow::Result;
|
||||
@ -8,7 +8,7 @@ use map_model::RoadID;
|
||||
use widgetry::{Choice, EventCtx};
|
||||
|
||||
use crate::shortcuts::find_shortcuts;
|
||||
use crate::{after_edit, App, Neighborhood};
|
||||
use crate::{after_edit, App, Neighbourhood};
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum Heuristic {
|
||||
@ -48,17 +48,17 @@ impl Heuristic {
|
||||
self,
|
||||
ctx: &EventCtx,
|
||||
app: &mut App,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
timer: &mut Timer,
|
||||
) -> Result<()> {
|
||||
if neighborhood
|
||||
if neighbourhood
|
||||
.cells
|
||||
.iter()
|
||||
.filter(|c| c.is_disconnected())
|
||||
.count()
|
||||
!= 0
|
||||
{
|
||||
bail!("This neighborhood has a disconnected cell; fix that first");
|
||||
bail!("This neighbourhood has a disconnected cell; fix that first");
|
||||
}
|
||||
|
||||
// TODO If we already have no shortcuts, stop
|
||||
@ -66,10 +66,10 @@ impl Heuristic {
|
||||
app.session.modal_filters.before_edit();
|
||||
|
||||
match self {
|
||||
Heuristic::Greedy => greedy(ctx, app, neighborhood, timer),
|
||||
Heuristic::BruteForce => brute_force(ctx, app, neighborhood, timer),
|
||||
Heuristic::SplitCells => split_cells(ctx, app, neighborhood, timer),
|
||||
Heuristic::OnlyOneBorder => only_one_border(app, neighborhood),
|
||||
Heuristic::Greedy => greedy(ctx, app, neighbourhood, timer),
|
||||
Heuristic::BruteForce => brute_force(ctx, app, neighbourhood, timer),
|
||||
Heuristic::SplitCells => split_cells(ctx, app, neighbourhood, timer),
|
||||
Heuristic::OnlyOneBorder => only_one_border(app, neighbourhood),
|
||||
}
|
||||
|
||||
let empty = app.session.modal_filters.cancel_empty_edit();
|
||||
@ -82,8 +82,8 @@ impl Heuristic {
|
||||
}
|
||||
}
|
||||
|
||||
fn greedy(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) {
|
||||
let shortcuts = find_shortcuts(app, &neighborhood, timer);
|
||||
fn greedy(ctx: &EventCtx, app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
|
||||
let shortcuts = find_shortcuts(app, &neighbourhood, timer);
|
||||
// TODO How should we break ties? Some shortcuts are worse than others; use that weight?
|
||||
// TODO Should this operation be per cell instead? We could hover on a road belonging to that
|
||||
// cell to select it
|
||||
@ -93,28 +93,28 @@ fn greedy(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mu
|
||||
.iter()
|
||||
.max_by_key(|pair| pair.1)
|
||||
{
|
||||
if try_to_filter_road(ctx, app, neighborhood, *r).is_none() {
|
||||
if try_to_filter_road(ctx, app, neighbourhood, *r).is_none() {
|
||||
warn!("Filtering {} disconnects a cell, never mind", r);
|
||||
// TODO Try the next choice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn brute_force(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) {
|
||||
fn brute_force(ctx: &EventCtx, app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
|
||||
// Which road leads to the fewest shortcuts?
|
||||
let mut best: Option<(RoadID, usize)> = None;
|
||||
|
||||
let orig_filters = app.session.modal_filters.roads.len();
|
||||
timer.start_iter(
|
||||
"evaluate candidate filters",
|
||||
neighborhood.orig_perimeter.interior.len(),
|
||||
neighbourhood.orig_perimeter.interior.len(),
|
||||
);
|
||||
for r in &neighborhood.orig_perimeter.interior {
|
||||
for r in &neighbourhood.orig_perimeter.interior {
|
||||
timer.next();
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
if let Some(new) = try_to_filter_road(ctx, app, neighborhood, *r) {
|
||||
if let Some(new) = try_to_filter_road(ctx, app, neighbourhood, *r) {
|
||||
let num_shortcuts =
|
||||
// This spams too many logs, and can't be used within a start_iter anyway
|
||||
find_shortcuts(app, &new, &mut Timer::throwaway())
|
||||
@ -132,27 +132,27 @@ fn brute_force(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer
|
||||
}
|
||||
|
||||
if let Some((r, _)) = best {
|
||||
try_to_filter_road(ctx, app, neighborhood, r).unwrap();
|
||||
try_to_filter_road(ctx, app, neighbourhood, r).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn split_cells(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer: &mut Timer) {
|
||||
fn split_cells(ctx: &EventCtx, app: &mut App, neighbourhood: &Neighbourhood, timer: &mut Timer) {
|
||||
// Filtering which road leads to new cells with the MOST streets in the smaller cell?
|
||||
let mut best: Option<(RoadID, usize)> = None;
|
||||
|
||||
let orig_filters = app.session.modal_filters.roads.len();
|
||||
timer.start_iter(
|
||||
"evaluate candidate filters",
|
||||
neighborhood.orig_perimeter.interior.len(),
|
||||
neighbourhood.orig_perimeter.interior.len(),
|
||||
);
|
||||
for r in &neighborhood.orig_perimeter.interior {
|
||||
for r in &neighbourhood.orig_perimeter.interior {
|
||||
timer.next();
|
||||
if app.session.modal_filters.roads.contains_key(r) {
|
||||
continue;
|
||||
}
|
||||
if let Some(new) = try_to_filter_road(ctx, app, neighborhood, *r) {
|
||||
if let Some(new) = try_to_filter_road(ctx, app, neighbourhood, *r) {
|
||||
// Did we split the cell?
|
||||
if new.cells.len() > neighborhood.cells.len() {
|
||||
if new.cells.len() > neighbourhood.cells.len() {
|
||||
// Find the two new cells
|
||||
let split_cells: Vec<_> = new
|
||||
.cells
|
||||
@ -178,12 +178,12 @@ fn split_cells(ctx: &EventCtx, app: &mut App, neighborhood: &Neighborhood, timer
|
||||
}
|
||||
|
||||
if let Some((r, _)) = best {
|
||||
try_to_filter_road(ctx, app, neighborhood, r).unwrap();
|
||||
try_to_filter_road(ctx, app, neighbourhood, r).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
fn only_one_border(app: &mut App, neighborhood: &Neighborhood) {
|
||||
for cell in &neighborhood.cells {
|
||||
fn only_one_border(app: &mut App, neighbourhood: &Neighbourhood) {
|
||||
for cell in &neighbourhood.cells {
|
||||
if cell.borders.len() > 1 {
|
||||
// TODO How to pick which one to leave open?
|
||||
for i in cell.borders.iter().skip(1) {
|
||||
@ -209,25 +209,25 @@ fn only_one_border(app: &mut App, neighborhood: &Neighborhood) {
|
||||
}
|
||||
}
|
||||
|
||||
// If successful, returns a Neighborhood and leaves the new filter in place. If it disconncts a
|
||||
// If successful, returns a Neighbourhood and leaves the new filter in place. If it disconncts a
|
||||
// cell, reverts the change and returns None
|
||||
fn try_to_filter_road(
|
||||
ctx: &EventCtx,
|
||||
app: &mut App,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
r: RoadID,
|
||||
) -> Option<Neighborhood> {
|
||||
) -> Option<Neighbourhood> {
|
||||
let road = app.map.get_r(r);
|
||||
app.session
|
||||
.modal_filters
|
||||
.roads
|
||||
.insert(r, road.length() / 2.0);
|
||||
// TODO This is expensive; can we just do the connectivity work and not drawing?
|
||||
let new_neighborhood = Neighborhood::new(ctx, app, neighborhood.id);
|
||||
if new_neighborhood.cells.iter().any(|c| c.is_disconnected()) {
|
||||
let new_neighbourhood = Neighbourhood::new(ctx, app, neighbourhood.id);
|
||||
if new_neighbourhood.cells.iter().any(|c| c.is_disconnected()) {
|
||||
app.session.modal_filters.roads.remove(&r).unwrap();
|
||||
None
|
||||
} else {
|
||||
Some(new_neighborhood)
|
||||
Some(new_neighbourhood)
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use widgetry::{
|
||||
};
|
||||
|
||||
use crate::impact::{end_of_day, Filters, Impact};
|
||||
use crate::{colors, App, BrowseNeighborhoods, Transition};
|
||||
use crate::{colors, App, BrowseNeighbourhoods, Transition};
|
||||
|
||||
// TODO Share structure or pieces with Ungap's predict mode
|
||||
// ... can't we just produce data of a certain shape, and have a UI pretty tuned for that?
|
||||
@ -47,7 +47,7 @@ impl ShowResults {
|
||||
}
|
||||
|
||||
let contents = Widget::col(vec![
|
||||
BrowseNeighborhoods::button(ctx, app),
|
||||
BrowseNeighbourhoods::button(ctx, app),
|
||||
Line("Impact prediction").small_heading().into_widget(ctx),
|
||||
Text::from(Line("This tool starts with a travel demand model, calculates the route every trip takes before and after changes, and displays volumes along roads")).wrap_to_pct(ctx, 20).into_widget(ctx),
|
||||
Text::from_all(vec![
|
||||
@ -79,10 +79,10 @@ impl State<App> for ShowResults {
|
||||
}
|
||||
match self.left_panel.event(ctx) {
|
||||
Outcome::Clicked(x) => match x.as_ref() {
|
||||
"Browse neighborhoods" => {
|
||||
"Browse neighbourhoods" => {
|
||||
// Don't just Pop; if we updated the results, the UI won't warn the user about a slow
|
||||
// loading
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
return Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app));
|
||||
}
|
||||
"Save before/after counts to files" => {
|
||||
let path1 = "counts_a.json";
|
||||
|
@ -6,11 +6,11 @@ use abstio::MapName;
|
||||
use abstutil::Timer;
|
||||
use widgetry::{EventCtx, GfxCtx, Settings};
|
||||
|
||||
pub use browse::BrowseNeighborhoods;
|
||||
pub use browse::BrowseNeighbourhoods;
|
||||
use filters::Toggle3Zoomed;
|
||||
pub use filters::{DiagonalFilter, ModalFilters};
|
||||
pub use neighborhood::{Cell, DistanceInterval, Neighborhood};
|
||||
pub use partition::{NeighborhoodID, Partitioning};
|
||||
pub use neighbourhood::{Cell, DistanceInterval, Neighbourhood};
|
||||
pub use partition::{NeighbourhoodID, Partitioning};
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
@ -27,7 +27,7 @@ mod edit;
|
||||
mod export;
|
||||
mod filters;
|
||||
mod impact;
|
||||
mod neighborhood;
|
||||
mod neighbourhood;
|
||||
mod partition;
|
||||
mod route_planner;
|
||||
mod save;
|
||||
@ -39,7 +39,7 @@ type App = map_gui::SimpleApp<Session>;
|
||||
type Transition = widgetry::Transition<App>;
|
||||
|
||||
pub fn main() {
|
||||
let settings = Settings::new("Low traffic neighborhoods");
|
||||
let settings = Settings::new("Low traffic neighbourhoods");
|
||||
run(settings);
|
||||
}
|
||||
|
||||
@ -48,7 +48,7 @@ struct Args {
|
||||
/// Load a previously saved proposal with this name. Note this takes a name, not a full path.
|
||||
#[structopt(long)]
|
||||
proposal: Option<String>,
|
||||
/// Lock the user into one fixed neighborhood, and remove many controls
|
||||
/// Lock the user into one fixed neighbourhood, and remove many controls
|
||||
#[structopt(long)]
|
||||
consultation: Option<String>,
|
||||
#[structopt(flatten)]
|
||||
@ -87,7 +87,7 @@ fn run(mut settings: Settings) {
|
||||
edit_filters: true,
|
||||
|
||||
highlight_boundary_roads: false,
|
||||
draw_neighborhood_style: browse::Style::SimpleColoring,
|
||||
draw_neighbourhood_style: browse::Style::SimpleColoring,
|
||||
draw_cells_as_areas: true,
|
||||
heuristic: filters::auto::Heuristic::SplitCells,
|
||||
main_road_penalty: 1.0,
|
||||
@ -103,7 +103,7 @@ fn run(mut settings: Settings) {
|
||||
args.app_args.cam,
|
||||
session,
|
||||
move |ctx, app| {
|
||||
// Restore the partitioning from a file before calling BrowseNeighborhoods
|
||||
// Restore the partitioning from a file before calling BrowseNeighbourhoods
|
||||
let popup_state = args.proposal.as_ref().and_then(|name| {
|
||||
crate::save::Proposal::load(
|
||||
ctx,
|
||||
@ -147,7 +147,7 @@ fn run(mut settings: Settings) {
|
||||
_ => panic!("Unknown Bristol consultation mode {consultation}"),
|
||||
};
|
||||
|
||||
// Look for the neighborhood containing one small street
|
||||
// Look for the neighbourhood containing one small street
|
||||
let r = app
|
||||
.map
|
||||
.all_roads()
|
||||
@ -155,16 +155,16 @@ fn run(mut settings: Settings) {
|
||||
.find(|r| r.get_name(None) == focus_on_street)
|
||||
.expect(&format!("Can't find {focus_on_street}"))
|
||||
.id;
|
||||
let (neighborhood, _) = app
|
||||
let (neighbourhood, _) = app
|
||||
.session
|
||||
.partitioning
|
||||
.all_neighborhoods()
|
||||
.all_neighbourhoods()
|
||||
.iter()
|
||||
.find(|(_, info)| info.block.perimeter.interior.contains(&r))
|
||||
.expect(&format!(
|
||||
"Can't find neighborhood containing {focus_on_street}"
|
||||
"Can't find neighbourhood containing {focus_on_street}"
|
||||
));
|
||||
app.session.consultation = Some(*neighborhood);
|
||||
app.session.consultation = Some(*neighbourhood);
|
||||
|
||||
// TODO Maybe center the camera, ignoring any saved values
|
||||
|
||||
@ -178,9 +178,9 @@ fn run(mut settings: Settings) {
|
||||
ctx,
|
||||
app,
|
||||
map_gui::tools::Executable::LTN,
|
||||
Box::new(|ctx, app, _| BrowseNeighborhoods::new_state(ctx, app)),
|
||||
Box::new(|ctx, app, _| BrowseNeighbourhoods::new_state(ctx, app)),
|
||||
));
|
||||
states.push(BrowseNeighborhoods::new_state(ctx, app));
|
||||
states.push(BrowseNeighbourhoods::new_state(ctx, app));
|
||||
}
|
||||
if let Some(state) = popup_state {
|
||||
states.push(state);
|
||||
@ -197,7 +197,7 @@ use wasm_bindgen::prelude::*;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
#[wasm_bindgen(js_name = "run")]
|
||||
pub fn run_wasm(root_dom_id: String, assets_base_url: String, assets_are_gzipped: bool) {
|
||||
let settings = Settings::new("Low traffic neighborhoods")
|
||||
let settings = Settings::new("Low traffic neighbourhoods")
|
||||
.root_dom_element_id(root_dom_id)
|
||||
.assets_base_url(assets_base_url)
|
||||
.assets_are_gzipped(assets_are_gzipped);
|
||||
@ -221,9 +221,9 @@ pub struct Session {
|
||||
pub edit_filters: bool,
|
||||
|
||||
// Remember form settings in different tabs.
|
||||
// Browse neighborhoods:
|
||||
// Browse neighbourhoods:
|
||||
pub highlight_boundary_roads: bool,
|
||||
pub draw_neighborhood_style: browse::Style,
|
||||
pub draw_neighbourhood_style: browse::Style,
|
||||
// Connectivity:
|
||||
pub draw_cells_as_areas: bool,
|
||||
pub heuristic: filters::auto::Heuristic,
|
||||
@ -232,7 +232,7 @@ pub struct Session {
|
||||
|
||||
current_trip_name: Option<String>,
|
||||
|
||||
consultation: Option<NeighborhoodID>,
|
||||
consultation: Option<NeighbourhoodID>,
|
||||
}
|
||||
|
||||
/// Do the equivalent of `SimpleApp::draw_unzoomed` or `draw_zoomed`, but after the water/park
|
||||
|
@ -7,10 +7,10 @@ use map_gui::tools::DrawRoadLabels;
|
||||
use map_model::{IntersectionID, Map, PathConstraints, Perimeter, RoadID};
|
||||
use widgetry::{Drawable, EventCtx, GeomBatch};
|
||||
|
||||
use crate::{App, ModalFilters, NeighborhoodID};
|
||||
use crate::{App, ModalFilters, NeighbourhoodID};
|
||||
|
||||
pub struct Neighborhood {
|
||||
pub id: NeighborhoodID,
|
||||
pub struct Neighbourhood {
|
||||
pub id: NeighbourhoodID,
|
||||
|
||||
// These're fixed
|
||||
pub orig_perimeter: Perimeter,
|
||||
@ -18,7 +18,7 @@ pub struct Neighborhood {
|
||||
pub borders: BTreeSet<IntersectionID>,
|
||||
pub interior_intersections: BTreeSet<IntersectionID>,
|
||||
|
||||
// The cells change as a result of modal filters, which're stored for all neighborhoods in
|
||||
// The cells change as a result of modal filters, which're stored for all neighbourhoods in
|
||||
// app.session.
|
||||
pub cells: Vec<Cell>,
|
||||
|
||||
@ -26,13 +26,13 @@ pub struct Neighborhood {
|
||||
pub labels: DrawRoadLabels,
|
||||
}
|
||||
|
||||
/// A partitioning of the interior of a neighborhood based on driving connectivity
|
||||
/// A partitioning of the interior of a neighbourhood based on driving connectivity
|
||||
pub struct Cell {
|
||||
/// Most roads are fully in one cell. Roads with modal filters on them are sometimes split
|
||||
/// between two cells, and the DistanceInterval indicates the split. The distances are over the
|
||||
/// road's center line length.
|
||||
pub roads: BTreeMap<RoadID, DistanceInterval>,
|
||||
/// Intersections where this cell touches the boundary of the neighborhood.
|
||||
/// Intersections where this cell touches the boundary of the neighbourhood.
|
||||
pub borders: BTreeSet<IntersectionID>,
|
||||
}
|
||||
|
||||
@ -49,17 +49,17 @@ pub struct DistanceInterval {
|
||||
pub end: Distance,
|
||||
}
|
||||
|
||||
impl Neighborhood {
|
||||
pub fn new(ctx: &EventCtx, app: &App, id: NeighborhoodID) -> Neighborhood {
|
||||
impl Neighbourhood {
|
||||
pub fn new(ctx: &EventCtx, app: &App, id: NeighbourhoodID) -> Neighbourhood {
|
||||
let map = &app.map;
|
||||
let orig_perimeter = app
|
||||
.session
|
||||
.partitioning
|
||||
.neighborhood_block(id)
|
||||
.neighbourhood_block(id)
|
||||
.perimeter
|
||||
.clone();
|
||||
|
||||
let mut n = Neighborhood {
|
||||
let mut n = Neighbourhood {
|
||||
id,
|
||||
orig_perimeter,
|
||||
perimeter: BTreeSet::new(),
|
||||
@ -84,7 +84,7 @@ impl Neighborhood {
|
||||
vec![app
|
||||
.session
|
||||
.partitioning
|
||||
.neighborhood_boundary_polygon(app, id)
|
||||
.neighbourhood_boundary_polygon(app, id)
|
||||
.into_ring()],
|
||||
);
|
||||
n.fade_irrelevant = GeomBatch::from(vec![(app.cs.fade_map_dark, fade_area)]).upload(ctx);
|
||||
@ -178,7 +178,7 @@ fn find_cells(
|
||||
fn floodfill(
|
||||
map: &Map,
|
||||
start: RoadID,
|
||||
neighborhood_borders: &BTreeSet<IntersectionID>,
|
||||
neighbourhood_borders: &BTreeSet<IntersectionID>,
|
||||
modal_filters: &ModalFilters,
|
||||
) -> Cell {
|
||||
let mut visited_roads: BTreeMap<RoadID, DistanceInterval> = BTreeMap::new();
|
||||
@ -204,10 +204,10 @@ fn floodfill(
|
||||
);
|
||||
for i in [current.src_i, current.dst_i] {
|
||||
// It's possible for one border intersection to have two roads in the interior of the
|
||||
// neighborhood. Don't consider a turn between those roads through this intersection as
|
||||
// neighbourhood. Don't consider a turn between those roads through this intersection as
|
||||
// counting as connectivity -- we're right at the boundary road, so it's like leaving
|
||||
// and re-entering the neighborhood.
|
||||
if neighborhood_borders.contains(&i) {
|
||||
// and re-entering the neighbourhood.
|
||||
if neighbourhood_borders.contains(&i) {
|
||||
cell_borders.insert(i);
|
||||
continue;
|
||||
}
|
@ -14,41 +14,41 @@ use crate::{colors, App};
|
||||
|
||||
/// An opaque ID, won't be contiguous as we adjust boundaries
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct NeighborhoodID(pub usize);
|
||||
pub struct NeighbourhoodID(pub usize);
|
||||
|
||||
/// Identifies a single / unmerged block, which never changes
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
||||
pub struct BlockID(usize);
|
||||
|
||||
// Some states want this
|
||||
impl widgetry::mapspace::ObjectID for NeighborhoodID {}
|
||||
impl widgetry::mapspace::ObjectID for NeighbourhoodID {}
|
||||
impl widgetry::mapspace::ObjectID for BlockID {}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct Partitioning {
|
||||
pub map: MapName,
|
||||
neighborhoods: BTreeMap<NeighborhoodID, NeighborhoodInfo>,
|
||||
neighbourhoods: BTreeMap<NeighbourhoodID, NeighbourhoodInfo>,
|
||||
// The single / unmerged blocks never change
|
||||
single_blocks: Vec<Block>,
|
||||
|
||||
neighborhood_id_counter: usize,
|
||||
neighbourhood_id_counter: usize,
|
||||
|
||||
// Invariant: This is a surjection, every block belongs to exactly one neighborhood
|
||||
block_to_neighborhood: BTreeMap<BlockID, NeighborhoodID>,
|
||||
// Invariant: This is a surjection, every block belongs to exactly one neighbourhood
|
||||
block_to_neighbourhood: BTreeMap<BlockID, NeighbourhoodID>,
|
||||
|
||||
use_expensive_blockfinding: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Serialize, Deserialize)]
|
||||
pub struct NeighborhoodInfo {
|
||||
pub struct NeighbourhoodInfo {
|
||||
pub block: Block,
|
||||
pub color: Color,
|
||||
/// Draw a special cone of light when focused on this neighborhood. It doesn't change which
|
||||
/// Draw a special cone of light when focused on this neighbourhood. It doesn't change which
|
||||
/// roads can be edited.
|
||||
pub override_drawing_boundary: Option<Polygon>,
|
||||
}
|
||||
|
||||
impl NeighborhoodInfo {
|
||||
impl NeighbourhoodInfo {
|
||||
fn new(block: Block) -> Self {
|
||||
Self {
|
||||
block,
|
||||
@ -63,19 +63,19 @@ impl Partitioning {
|
||||
pub fn empty() -> Partitioning {
|
||||
Partitioning {
|
||||
map: MapName::new("zz", "temp", "orary"),
|
||||
neighborhoods: BTreeMap::new(),
|
||||
neighbourhoods: BTreeMap::new(),
|
||||
single_blocks: Vec::new(),
|
||||
|
||||
neighborhood_id_counter: 0,
|
||||
neighbourhood_id_counter: 0,
|
||||
|
||||
block_to_neighborhood: BTreeMap::new(),
|
||||
block_to_neighbourhood: BTreeMap::new(),
|
||||
|
||||
use_expensive_blockfinding: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.neighborhoods.is_empty()
|
||||
self.neighbourhoods.is_empty()
|
||||
}
|
||||
|
||||
pub fn seed_using_heuristics(app: &App, timer: &mut Timer) -> Partitioning {
|
||||
@ -99,7 +99,7 @@ impl Partitioning {
|
||||
|
||||
timer.start("partition");
|
||||
let partitions = Perimeter::partition_by_predicate(single_block_perims, |r| {
|
||||
// "Interior" roads of a neighborhood aren't classified as arterial
|
||||
// "Interior" roads of a neighbourhood aren't classified as arterial
|
||||
map.get_r(r).get_rank() == RoadRank::Local
|
||||
});
|
||||
|
||||
@ -130,40 +130,40 @@ impl Partitioning {
|
||||
}
|
||||
}
|
||||
|
||||
let mut neighborhoods = BTreeMap::new();
|
||||
let mut neighbourhoods = BTreeMap::new();
|
||||
for block in blocks {
|
||||
neighborhoods.insert(
|
||||
NeighborhoodID(neighborhoods.len()),
|
||||
NeighborhoodInfo::new(block),
|
||||
neighbourhoods.insert(
|
||||
NeighbourhoodID(neighbourhoods.len()),
|
||||
NeighbourhoodInfo::new(block),
|
||||
);
|
||||
}
|
||||
let neighborhood_id_counter = neighborhoods.len();
|
||||
let neighbourhood_id_counter = neighbourhoods.len();
|
||||
let mut p = Partitioning {
|
||||
map: map.get_name().clone(),
|
||||
neighborhoods,
|
||||
neighbourhoods,
|
||||
single_blocks,
|
||||
|
||||
neighborhood_id_counter,
|
||||
block_to_neighborhood: BTreeMap::new(),
|
||||
neighbourhood_id_counter,
|
||||
block_to_neighbourhood: BTreeMap::new(),
|
||||
use_expensive_blockfinding,
|
||||
};
|
||||
|
||||
// TODO We could probably build this up as we go
|
||||
for id in p.all_block_ids() {
|
||||
if let Some(neighborhood) = p.neighborhood_containing(id) {
|
||||
p.block_to_neighborhood.insert(id, neighborhood);
|
||||
if let Some(neighbourhood) = p.neighbourhood_containing(id) {
|
||||
p.block_to_neighbourhood.insert(id, neighbourhood);
|
||||
} else {
|
||||
if !use_expensive_blockfinding {
|
||||
// Try the expensive check, then
|
||||
error!(
|
||||
"Block doesn't belong to any neighborhood? Retrying with expensive checks {:?}",
|
||||
"Block doesn't belong to any neighbourhood? Retrying with expensive checks {:?}",
|
||||
p.get_block(id).perimeter
|
||||
);
|
||||
continue 'METHOD;
|
||||
}
|
||||
// This will break everything downstream, so bail out immediately
|
||||
panic!(
|
||||
"Block doesn't belong to any neighborhood?! {:?}",
|
||||
"Block doesn't belong to any neighbourhood?! {:?}",
|
||||
p.get_block(id).perimeter
|
||||
);
|
||||
}
|
||||
@ -178,18 +178,25 @@ impl Partitioning {
|
||||
/// True if the coloring changed
|
||||
pub fn recalculate_coloring(&mut self) -> bool {
|
||||
let perims: Vec<Perimeter> = self
|
||||
.neighborhoods
|
||||
.neighbourhoods
|
||||
.values()
|
||||
.map(|info| info.block.perimeter.clone())
|
||||
.collect();
|
||||
let colors = Perimeter::calculate_coloring(&perims, colors::NEIGHBORHOODS.len())
|
||||
let colors = Perimeter::calculate_coloring(&perims, colors::NEIGHBOURHOODS.len())
|
||||
.unwrap_or_else(|| (0..perims.len()).collect());
|
||||
let orig_coloring: Vec<Color> =
|
||||
self.neighborhoods.values().map(|info| info.color).collect();
|
||||
for (info, color_idx) in self.neighborhoods.values_mut().zip(colors.into_iter()) {
|
||||
info.color = colors::NEIGHBORHOODS[color_idx % colors::NEIGHBORHOODS.len()];
|
||||
let orig_coloring: Vec<Color> = self
|
||||
.neighbourhoods
|
||||
.values()
|
||||
.map(|info| info.color)
|
||||
.collect();
|
||||
for (info, color_idx) in self.neighbourhoods.values_mut().zip(colors.into_iter()) {
|
||||
info.color = colors::NEIGHBOURHOODS[color_idx % colors::NEIGHBOURHOODS.len()];
|
||||
}
|
||||
let new_coloring: Vec<Color> = self.neighborhoods.values().map(|info| info.color).collect();
|
||||
let new_coloring: Vec<Color> = self
|
||||
.neighbourhoods
|
||||
.values()
|
||||
.map(|info| info.color)
|
||||
.collect();
|
||||
orig_coloring != new_coloring
|
||||
}
|
||||
|
||||
@ -198,38 +205,38 @@ impl Partitioning {
|
||||
&mut self,
|
||||
map: &Map,
|
||||
id: BlockID,
|
||||
old_owner: NeighborhoodID,
|
||||
new_owner: NeighborhoodID,
|
||||
) -> Result<Option<NeighborhoodID>> {
|
||||
old_owner: NeighbourhoodID,
|
||||
new_owner: NeighbourhoodID,
|
||||
) -> Result<Option<NeighbourhoodID>> {
|
||||
assert_ne!(old_owner, new_owner);
|
||||
|
||||
// Is the newly expanded neighborhood a valid perimeter?
|
||||
// Is the newly expanded neighbourhood a valid perimeter?
|
||||
let new_owner_blocks: Vec<BlockID> = self
|
||||
.block_to_neighborhood
|
||||
.block_to_neighbourhood
|
||||
.iter()
|
||||
.filter_map(|(block, neighborhood)| {
|
||||
if *neighborhood == new_owner || *block == id {
|
||||
.filter_map(|(block, neighbourhood)| {
|
||||
if *neighbourhood == new_owner || *block == id {
|
||||
Some(*block)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
let mut new_neighborhood_blocks = self.make_merged_blocks(map, new_owner_blocks)?;
|
||||
if new_neighborhood_blocks.len() != 1 {
|
||||
let mut new_neighbourhood_blocks = self.make_merged_blocks(map, new_owner_blocks)?;
|
||||
if new_neighbourhood_blocks.len() != 1 {
|
||||
// This happens when a hole would be created by adding this block. There are probably
|
||||
// some smaller blocks nearby to add first.
|
||||
bail!("Couldn't add block -- you may need to add an intermediate block first to avoid a hole, or there's a bug you can't workaround yet");
|
||||
}
|
||||
let new_neighborhood_block = new_neighborhood_blocks.pop().unwrap();
|
||||
let new_neighbourhood_block = new_neighbourhood_blocks.pop().unwrap();
|
||||
|
||||
// Is the old neighborhood, minus this block, still valid?
|
||||
// TODO refactor Neighborhood to BlockIDs?
|
||||
// Is the old neighbourhood, minus this block, still valid?
|
||||
// TODO refactor Neighbourhood to BlockIDs?
|
||||
let old_owner_blocks: Vec<BlockID> = self
|
||||
.block_to_neighborhood
|
||||
.block_to_neighbourhood
|
||||
.iter()
|
||||
.filter_map(|(block, neighborhood)| {
|
||||
if *neighborhood == old_owner && *block != id {
|
||||
.filter_map(|(block, neighbourhood)| {
|
||||
if *neighbourhood == old_owner && *block != id {
|
||||
Some(*block)
|
||||
} else {
|
||||
None
|
||||
@ -237,55 +244,56 @@ impl Partitioning {
|
||||
})
|
||||
.collect();
|
||||
if old_owner_blocks.is_empty() {
|
||||
// We're deleting the old neighborhood!
|
||||
self.neighborhoods.get_mut(&new_owner).unwrap().block = new_neighborhood_block;
|
||||
self.neighborhoods.remove(&old_owner).unwrap();
|
||||
self.block_to_neighborhood.insert(id, new_owner);
|
||||
// Tell the caller to recreate this SelectBoundary state, switching to the neighborhood
|
||||
// We're deleting the old neighbourhood!
|
||||
self.neighbourhoods.get_mut(&new_owner).unwrap().block = new_neighbourhood_block;
|
||||
self.neighbourhoods.remove(&old_owner).unwrap();
|
||||
self.block_to_neighbourhood.insert(id, new_owner);
|
||||
// Tell the caller to recreate this SelectBoundary state, switching to the neighbourhood
|
||||
// we just donated to, since the old is now gone
|
||||
return Ok(Some(new_owner));
|
||||
}
|
||||
|
||||
let mut old_neighborhood_blocks = self.make_merged_blocks(map, old_owner_blocks.clone())?;
|
||||
// We might be splitting the old neighborhood into multiple pieces! Pick the largest piece
|
||||
// as the old_owner (so the UI for trimming a neighborhood is less jarring), and create new
|
||||
// neighborhoods for the others.
|
||||
old_neighborhood_blocks.sort_by_key(|block| block.perimeter.interior.len());
|
||||
self.neighborhoods.get_mut(&old_owner).unwrap().block =
|
||||
old_neighborhood_blocks.pop().unwrap();
|
||||
let new_splits = !old_neighborhood_blocks.is_empty();
|
||||
for split_piece in old_neighborhood_blocks {
|
||||
let new_neighborhood = NeighborhoodID(self.neighborhood_id_counter);
|
||||
self.neighborhood_id_counter += 1;
|
||||
let mut old_neighbourhood_blocks =
|
||||
self.make_merged_blocks(map, old_owner_blocks.clone())?;
|
||||
// We might be splitting the old neighbourhood into multiple pieces! Pick the largest piece
|
||||
// as the old_owner (so the UI for trimming a neighbourhood is less jarring), and create new
|
||||
// neighbourhoods for the others.
|
||||
old_neighbourhood_blocks.sort_by_key(|block| block.perimeter.interior.len());
|
||||
self.neighbourhoods.get_mut(&old_owner).unwrap().block =
|
||||
old_neighbourhood_blocks.pop().unwrap();
|
||||
let new_splits = !old_neighbourhood_blocks.is_empty();
|
||||
for split_piece in old_neighbourhood_blocks {
|
||||
let new_neighbourhood = NeighbourhoodID(self.neighbourhood_id_counter);
|
||||
self.neighbourhood_id_counter += 1;
|
||||
// Temporary color
|
||||
self.neighborhoods
|
||||
.insert(new_neighborhood, NeighborhoodInfo::new(split_piece));
|
||||
self.neighbourhoods
|
||||
.insert(new_neighbourhood, NeighbourhoodInfo::new(split_piece));
|
||||
}
|
||||
if new_splits {
|
||||
// We need to update the owner of all single blocks in these new pieces
|
||||
for id in old_owner_blocks {
|
||||
self.block_to_neighborhood
|
||||
.insert(id, self.neighborhood_containing(id).unwrap());
|
||||
self.block_to_neighbourhood
|
||||
.insert(id, self.neighbourhood_containing(id).unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
self.neighborhoods.get_mut(&new_owner).unwrap().block = new_neighborhood_block;
|
||||
self.block_to_neighborhood.insert(id, new_owner);
|
||||
self.neighbourhoods.get_mut(&new_owner).unwrap().block = new_neighbourhood_block;
|
||||
self.block_to_neighbourhood.insert(id, new_owner);
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
/// Needs to find an existing neighborhood to take the block, or make a new one
|
||||
pub fn remove_block_from_neighborhood(
|
||||
/// Needs to find an existing neighbourhood to take the block, or make a new one
|
||||
pub fn remove_block_from_neighbourhood(
|
||||
&mut self,
|
||||
map: &Map,
|
||||
id: BlockID,
|
||||
old_owner: NeighborhoodID,
|
||||
) -> Result<Option<NeighborhoodID>> {
|
||||
// Find all RoadSideIDs in the block matching the current neighborhood perimeter. Look for
|
||||
// the first one that borders another neighborhood, and transfer the block there.
|
||||
old_owner: NeighbourhoodID,
|
||||
) -> Result<Option<NeighbourhoodID>> {
|
||||
// Find all RoadSideIDs in the block matching the current neighbourhood perimeter. Look for
|
||||
// the first one that borders another neighbourhood, and transfer the block there.
|
||||
// TODO This can get unintuitive -- if we remove a block bordering two other
|
||||
// neighborhoods, which one should we donate to?
|
||||
let current_perim_set: BTreeSet<RoadSideID> = self.neighborhoods[&old_owner]
|
||||
// neighbourhoods, which one should we donate to?
|
||||
let current_perim_set: BTreeSet<RoadSideID> = self.neighbourhoods[&old_owner]
|
||||
.block
|
||||
.perimeter
|
||||
.roads
|
||||
@ -296,11 +304,11 @@ impl Partitioning {
|
||||
if !current_perim_set.contains(road_side) {
|
||||
continue;
|
||||
}
|
||||
// Is there another neighborhood that has the other side of this road on its perimeter?
|
||||
// TODO We could map road -> BlockID then use block_to_neighborhood
|
||||
// Is there another neighbourhood that has the other side of this road on its perimeter?
|
||||
// TODO We could map road -> BlockID then use block_to_neighbourhood
|
||||
let other_side = road_side.other_side();
|
||||
if let Some((new_owner, _)) = self
|
||||
.neighborhoods
|
||||
.neighbourhoods
|
||||
.iter()
|
||||
.find(|(_, info)| info.block.perimeter.roads.contains(&other_side))
|
||||
{
|
||||
@ -310,16 +318,18 @@ impl Partitioning {
|
||||
}
|
||||
|
||||
// We didn't find any match, so we're jettisoning a block near the edge of the map (or a
|
||||
// buggy area missing blocks). Create a new neighborhood with just this block.
|
||||
let new_owner = NeighborhoodID(self.neighborhood_id_counter);
|
||||
self.neighborhood_id_counter += 1;
|
||||
// buggy area missing blocks). Create a new neighbourhood with just this block.
|
||||
let new_owner = NeighbourhoodID(self.neighbourhood_id_counter);
|
||||
self.neighbourhood_id_counter += 1;
|
||||
// Temporary color
|
||||
self.neighborhoods
|
||||
.insert(new_owner, NeighborhoodInfo::new(self.get_block(id).clone()));
|
||||
self.neighbourhoods.insert(
|
||||
new_owner,
|
||||
NeighbourhoodInfo::new(self.get_block(id).clone()),
|
||||
);
|
||||
let result = self.transfer_block(map, id, old_owner, new_owner);
|
||||
if result.is_err() {
|
||||
// Revert the change above!
|
||||
self.neighborhoods.remove(&new_owner).unwrap();
|
||||
self.neighbourhoods.remove(&new_owner).unwrap();
|
||||
}
|
||||
result
|
||||
}
|
||||
@ -327,26 +337,26 @@ impl Partitioning {
|
||||
|
||||
// Read-only
|
||||
impl Partitioning {
|
||||
pub fn neighborhood_block(&self, id: NeighborhoodID) -> &Block {
|
||||
&self.neighborhoods[&id].block
|
||||
pub fn neighbourhood_block(&self, id: NeighbourhoodID) -> &Block {
|
||||
&self.neighbourhoods[&id].block
|
||||
}
|
||||
|
||||
pub fn neighborhood_area_km2(&self, id: NeighborhoodID) -> String {
|
||||
pub fn neighbourhood_area_km2(&self, id: NeighbourhoodID) -> String {
|
||||
// Convert from m^2 to km^2
|
||||
let area = self.neighborhood_block(id).polygon.area() / 1_000_000.0;
|
||||
let area = self.neighbourhood_block(id).polygon.area() / 1_000_000.0;
|
||||
format!("~{:.1} km²", area)
|
||||
}
|
||||
|
||||
pub fn neighborhood_color(&self, id: NeighborhoodID) -> Color {
|
||||
self.neighborhoods[&id].color
|
||||
pub fn neighbourhood_color(&self, id: NeighbourhoodID) -> Color {
|
||||
self.neighbourhoods[&id].color
|
||||
}
|
||||
|
||||
pub fn neighborhood_boundary_polygon(&self, app: &App, id: NeighborhoodID) -> Polygon {
|
||||
let info = &self.neighborhoods[&id];
|
||||
pub fn neighbourhood_boundary_polygon(&self, app: &App, id: NeighbourhoodID) -> Polygon {
|
||||
let info = &self.neighbourhoods[&id];
|
||||
if let Some(polygon) = info.override_drawing_boundary.clone() {
|
||||
return polygon;
|
||||
}
|
||||
// The neighborhood's perimeter hugs the "interior" of the neighborhood. If we just use the
|
||||
// The neighbourhood's perimeter hugs the "interior" of the neighbourhood. If we just use the
|
||||
// other side of the perimeter road, the highlighted area nicely shows the boundary road
|
||||
// too. (But sometimes this breaks, of course)
|
||||
match info
|
||||
@ -360,22 +370,26 @@ impl Partitioning {
|
||||
Err(_) => info.block.polygon.clone(),
|
||||
}
|
||||
}
|
||||
pub fn override_neighborhood_boundary_polygon(&mut self, id: NeighborhoodID, polygon: Polygon) {
|
||||
self.neighborhoods
|
||||
pub fn override_neighbourhood_boundary_polygon(
|
||||
&mut self,
|
||||
id: NeighbourhoodID,
|
||||
polygon: Polygon,
|
||||
) {
|
||||
self.neighbourhoods
|
||||
.get_mut(&id)
|
||||
.unwrap()
|
||||
.override_drawing_boundary = Some(polygon);
|
||||
}
|
||||
|
||||
pub fn all_neighborhoods(&self) -> &BTreeMap<NeighborhoodID, NeighborhoodInfo> {
|
||||
&self.neighborhoods
|
||||
pub fn all_neighbourhoods(&self) -> &BTreeMap<NeighbourhoodID, NeighbourhoodInfo> {
|
||||
&self.neighbourhoods
|
||||
}
|
||||
|
||||
// Just used for initial creation
|
||||
fn neighborhood_containing(&self, find_block: BlockID) -> Option<NeighborhoodID> {
|
||||
fn neighbourhood_containing(&self, find_block: BlockID) -> Option<NeighbourhoodID> {
|
||||
// TODO We could probably build this mapping up when we do Perimeter::merge_all
|
||||
let find_block = self.get_block(find_block);
|
||||
for (id, info) in &self.neighborhoods {
|
||||
for (id, info) in &self.neighbourhoods {
|
||||
if info.block.perimeter.contains(&find_block.perimeter) {
|
||||
return Some(*id);
|
||||
}
|
||||
@ -399,13 +413,13 @@ impl Partitioning {
|
||||
&self.single_blocks[id.0]
|
||||
}
|
||||
|
||||
pub fn block_to_neighborhood(&self, id: BlockID) -> NeighborhoodID {
|
||||
self.block_to_neighborhood[&id]
|
||||
pub fn block_to_neighbourhood(&self, id: BlockID) -> NeighbourhoodID {
|
||||
self.block_to_neighbourhood[&id]
|
||||
}
|
||||
|
||||
pub fn all_blocks_in_neighborhood(&self, id: NeighborhoodID) -> Vec<BlockID> {
|
||||
pub fn all_blocks_in_neighbourhood(&self, id: NeighbourhoodID) -> Vec<BlockID> {
|
||||
let mut result = Vec::new();
|
||||
for (block, n) in &self.block_to_neighborhood {
|
||||
for (block, n) in &self.block_to_neighbourhood {
|
||||
if *n == id {
|
||||
result.push(*block);
|
||||
}
|
||||
@ -413,9 +427,9 @@ impl Partitioning {
|
||||
result
|
||||
}
|
||||
|
||||
pub fn some_block_in_neighborhood(&self, id: NeighborhoodID) -> BlockID {
|
||||
for (block, neighborhood) in &self.block_to_neighborhood {
|
||||
if id == *neighborhood {
|
||||
pub fn some_block_in_neighbourhood(&self, id: NeighbourhoodID) -> BlockID {
|
||||
for (block, neighbourhood) in &self.block_to_neighbourhood {
|
||||
if id == *neighbourhood {
|
||||
return *block;
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use widgetry::{
|
||||
RoundedF64, Spinner, State, Text, Widget,
|
||||
};
|
||||
|
||||
use crate::{colors, App, BrowseNeighborhoods, Transition};
|
||||
use crate::{colors, App, BrowseNeighbourhoods, Transition};
|
||||
|
||||
pub struct RoutePlanner {
|
||||
top_panel: Panel,
|
||||
@ -76,7 +76,7 @@ impl RoutePlanner {
|
||||
|
||||
let contents = Widget::col(vec![
|
||||
app.session.alt_proposals.to_widget(ctx, app),
|
||||
BrowseNeighborhoods::button(ctx, app),
|
||||
BrowseNeighbourhoods::button(ctx, app),
|
||||
ctx.style()
|
||||
.btn_back("Analyze neighbourhood")
|
||||
.hotkey(Key::Escape)
|
||||
@ -119,9 +119,9 @@ impl RoutePlanner {
|
||||
panel.restore(ctx, &self.left_panel);
|
||||
self.left_panel = panel;
|
||||
|
||||
// Fade all neighborhood interiors, so it's very clear when a route cuts through
|
||||
// Fade all neighbourhood interiors, so it's very clear when a route cuts through
|
||||
let mut batch = GeomBatch::new();
|
||||
for info in app.session.partitioning.all_neighborhoods().values() {
|
||||
for info in app.session.partitioning.all_neighbourhoods().values() {
|
||||
batch.push(app.cs.fade_map_dark, info.block.polygon.clone());
|
||||
}
|
||||
|
||||
@ -265,8 +265,8 @@ impl State<App> for RoutePlanner {
|
||||
|
||||
let panel_outcome = self.left_panel.event(ctx);
|
||||
if let Outcome::Clicked(ref x) = panel_outcome {
|
||||
if x == "Browse neighborhoods" {
|
||||
return Transition::Replace(BrowseNeighborhoods::new_state(ctx, app));
|
||||
if x == "Browse neighbourhoods" {
|
||||
return Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app));
|
||||
}
|
||||
if x == "Analyze neighbourhood" {
|
||||
return Transition::Pop;
|
||||
@ -326,7 +326,7 @@ fn help() -> Vec<&'static str> {
|
||||
vec![
|
||||
"You can test how different driving routes are affected by proposed LTNs.",
|
||||
"",
|
||||
"The fastest route may not cut through neighborhoods normally,",
|
||||
"The fastest route may not cut through neighbourhoods normally,",
|
||||
"but you can adjust the slow-down factor to mimic rush hour conditions",
|
||||
]
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ use widgetry::tools::PopupMsg;
|
||||
use widgetry::{Choice, EventCtx, Key, Line, State, Widget};
|
||||
|
||||
use crate::partition::BlockID;
|
||||
use crate::{App, BrowseNeighborhoods, ModalFilters, Partitioning, Transition};
|
||||
use crate::{App, BrowseNeighbourhoods, ModalFilters, Partitioning, Transition};
|
||||
|
||||
/// Captures all of the edits somebody makes to a map in the LTN tool. Note this is separate from
|
||||
/// `map_model::MapEdits`.
|
||||
@ -307,11 +307,11 @@ impl AltProposals {
|
||||
|
||||
// After switching proposals, we have to recreate state
|
||||
//
|
||||
// To preserve per-neigbhorhood states, we have to transform neighborhood IDs, which may change if
|
||||
// To preserve per-neigbhorhood states, we have to transform neighbourhood IDs, which may change if
|
||||
// the partitioning is different. If the boundary is a bit different, match up by all the blocks in
|
||||
// the current neighborhood.
|
||||
// the current neighbourhood.
|
||||
pub enum PreserveState {
|
||||
BrowseNeighborhoods,
|
||||
BrowseNeighbourhoods,
|
||||
Route,
|
||||
Connectivity(Vec<BlockID>),
|
||||
Shortcuts(Option<PathRequest>, Vec<BlockID>),
|
||||
@ -320,18 +320,18 @@ pub enum PreserveState {
|
||||
impl PreserveState {
|
||||
fn switch_to_state(self, ctx: &mut EventCtx, app: &mut App) -> Transition {
|
||||
match self {
|
||||
PreserveState::BrowseNeighborhoods => {
|
||||
Transition::Replace(BrowseNeighborhoods::new_state(ctx, app))
|
||||
PreserveState::BrowseNeighbourhoods => {
|
||||
Transition::Replace(BrowseNeighbourhoods::new_state(ctx, app))
|
||||
}
|
||||
PreserveState::Route => {
|
||||
Transition::Replace(crate::route_planner::RoutePlanner::new_state(ctx, app))
|
||||
}
|
||||
PreserveState::Connectivity(blocks) => {
|
||||
// Count which new neighborhoods have the blocks from the original. Pick the one
|
||||
// Count which new neighbourhoods have the blocks from the original. Pick the one
|
||||
// with the most matches
|
||||
let mut count = Counter::new();
|
||||
for block in blocks {
|
||||
count.inc(app.session.partitioning.block_to_neighborhood(block));
|
||||
count.inc(app.session.partitioning.block_to_neighbourhood(block));
|
||||
}
|
||||
Transition::Replace(crate::connectivity::Viewer::new_state(
|
||||
ctx,
|
||||
@ -342,7 +342,7 @@ impl PreserveState {
|
||||
PreserveState::Shortcuts(req, blocks) => {
|
||||
let mut count = Counter::new();
|
||||
for block in blocks {
|
||||
count.inc(app.session.partitioning.block_to_neighborhood(block));
|
||||
count.inc(app.session.partitioning.block_to_neighbourhood(block));
|
||||
}
|
||||
Transition::Replace(crate::shortcut_viewer::BrowseShortcuts::new_state(
|
||||
ctx,
|
||||
|
@ -78,8 +78,8 @@ fn is_road_id(path: &str) -> bool {
|
||||
Regex::new(r"^/partitioning/single_blocks/\d+/perimeter/interior/\d+$").unwrap(),
|
||||
Regex::new(r"^/partitioning/single_blocks/\d+/perimeter/roads/\d+/road$").unwrap(),
|
||||
// The other
|
||||
Regex::new(r"^/partitioning/neighborhoods/\d+/0/perimeter/interior/\d+$").unwrap(),
|
||||
Regex::new(r"^/partitioning/neighborhoods/\d+/0/perimeter/roads/\d+/road$").unwrap(),
|
||||
Regex::new(r"^/partitioning/neighbourhoods/\d+/0/perimeter/interior/\d+$").unwrap(),
|
||||
Regex::new(r"^/partitioning/neighbourhoods/\d+/0/perimeter/roads/\d+/road$").unwrap(),
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,12 @@ use widgetry::{
|
||||
|
||||
use crate::browse::draw_boundary_roads;
|
||||
use crate::partition::BlockID;
|
||||
use crate::{colors, App, NeighborhoodID, Partitioning, Transition};
|
||||
use crate::{colors, App, NeighbourhoodID, Partitioning, Transition};
|
||||
|
||||
pub struct SelectBoundary {
|
||||
top_panel: Panel,
|
||||
left_panel: Panel,
|
||||
id: NeighborhoodID,
|
||||
id: NeighbourhoodID,
|
||||
world: World<BlockID>,
|
||||
draw_boundary_roads: ToggleZoomed,
|
||||
frontier: BTreeSet<BlockID>,
|
||||
@ -35,7 +35,7 @@ pub struct SelectBoundary {
|
||||
}
|
||||
|
||||
impl SelectBoundary {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighborhoodID) -> Box<dyn State<App>> {
|
||||
pub fn new_state(ctx: &mut EventCtx, app: &App, id: NeighbourhoodID) -> Box<dyn State<App>> {
|
||||
let top_panel = crate::components::TopPanel::panel(ctx, app);
|
||||
let left_panel = make_panel(ctx, app, id, &top_panel);
|
||||
let mut state = SelectBoundary {
|
||||
@ -54,7 +54,7 @@ impl SelectBoundary {
|
||||
lasso: None,
|
||||
};
|
||||
|
||||
let initial_boundary = app.session.partitioning.neighborhood_block(id);
|
||||
let initial_boundary = app.session.partitioning.neighbourhood_block(id);
|
||||
state.frontier = app
|
||||
.session
|
||||
.partitioning
|
||||
@ -104,7 +104,7 @@ impl SelectBoundary {
|
||||
}
|
||||
}
|
||||
|
||||
// If the block is part of the current neighborhood, remove it. Otherwise add it. It's assumed
|
||||
// If the block is part of the current neighbourhood, remove it. Otherwise add it. It's assumed
|
||||
// this block is in the previous frontier
|
||||
fn toggle_block(&mut self, ctx: &mut EventCtx, app: &mut App, id: BlockID) -> Transition {
|
||||
if self.last_failed_change == Some((id, self.currently_have_block(app, id))) {
|
||||
@ -113,16 +113,16 @@ impl SelectBoundary {
|
||||
self.last_failed_change = None;
|
||||
|
||||
match self.try_toggle_block(app, id) {
|
||||
Ok(Some(new_neighborhood)) => {
|
||||
Ok(Some(new_neighbourhood)) => {
|
||||
app.session.partitioning.recalculate_coloring();
|
||||
return Transition::Replace(SelectBoundary::new_state(ctx, app, new_neighborhood));
|
||||
return Transition::Replace(SelectBoundary::new_state(ctx, app, new_neighbourhood));
|
||||
}
|
||||
Ok(None) => {
|
||||
let old_frontier = std::mem::take(&mut self.frontier);
|
||||
self.frontier = app.session.partitioning.calculate_frontier(
|
||||
&app.session
|
||||
.partitioning
|
||||
.neighborhood_block(self.id)
|
||||
.neighbourhood_block(self.id)
|
||||
.perimeter,
|
||||
);
|
||||
|
||||
@ -135,7 +135,7 @@ impl SelectBoundary {
|
||||
changed_blocks.push(id);
|
||||
|
||||
if app.session.partitioning.recalculate_coloring() {
|
||||
// The coloring of neighborhoods changed; this could possibly have impact far
|
||||
// The coloring of neighbourhoods changed; this could possibly have impact far
|
||||
// away. Just redraw all blocks.
|
||||
changed_blocks.clear();
|
||||
changed_blocks.extend(app.session.partitioning.all_block_ids());
|
||||
@ -161,16 +161,16 @@ impl SelectBoundary {
|
||||
Transition::Keep
|
||||
}
|
||||
|
||||
// Ok(Some(x)) means the current neighborhood was destroyed, and the caller should switch to
|
||||
// Ok(Some(x)) means the current neighbourhood was destroyed, and the caller should switch to
|
||||
// focusing on a different neigbhorhood
|
||||
fn try_toggle_block(&mut self, app: &mut App, id: BlockID) -> Result<Option<NeighborhoodID>> {
|
||||
fn try_toggle_block(&mut self, app: &mut App, id: BlockID) -> Result<Option<NeighbourhoodID>> {
|
||||
if self.currently_have_block(app, id) {
|
||||
app.session
|
||||
.partitioning
|
||||
.remove_block_from_neighborhood(&app.map, id, self.id)
|
||||
.remove_block_from_neighbourhood(&app.map, id, self.id)
|
||||
} else {
|
||||
let old_owner = app.session.partitioning.block_to_neighborhood(id);
|
||||
// Ignore the return value if the old neighborhood is deleted
|
||||
let old_owner = app.session.partitioning.block_to_neighbourhood(id);
|
||||
// Ignore the return value if the old neighbourhood is deleted
|
||||
app.session
|
||||
.partitioning
|
||||
.transfer_block(&app.map, id, old_owner, self.id)?;
|
||||
@ -179,17 +179,17 @@ impl SelectBoundary {
|
||||
}
|
||||
|
||||
fn currently_have_block(&self, app: &App, id: BlockID) -> bool {
|
||||
app.session.partitioning.block_to_neighborhood(id) == self.id
|
||||
app.session.partitioning.block_to_neighbourhood(id) == self.id
|
||||
}
|
||||
|
||||
fn add_blocks_freehand(&mut self, ctx: &mut EventCtx, app: &mut App, lasso_polygon: Polygon) {
|
||||
ctx.loading_screen("expand current neighborhood boundary", |ctx, timer| {
|
||||
ctx.loading_screen("expand current neighbourhood boundary", |ctx, timer| {
|
||||
timer.start("find matching blocks");
|
||||
// Find all of the blocks within the polygon
|
||||
let mut add_blocks = Vec::new();
|
||||
for (id, block) in app.session.partitioning.all_single_blocks() {
|
||||
if lasso_polygon.contains_pt(block.polygon.center()) {
|
||||
if app.session.partitioning.block_to_neighborhood(id) != self.id {
|
||||
if app.session.partitioning.block_to_neighbourhood(id) != self.id {
|
||||
add_blocks.push(id);
|
||||
}
|
||||
}
|
||||
@ -208,7 +208,7 @@ impl SelectBoundary {
|
||||
for block_id in add_blocks.drain(..) {
|
||||
timer.next();
|
||||
if self.frontier.contains(&block_id) {
|
||||
let old_owner = app.session.partitioning.block_to_neighborhood(block_id);
|
||||
let old_owner = app.session.partitioning.block_to_neighbourhood(block_id);
|
||||
if let Ok(_) = app
|
||||
.session
|
||||
.partitioning
|
||||
@ -227,7 +227,7 @@ impl SelectBoundary {
|
||||
self.frontier = app.session.partitioning.calculate_frontier(
|
||||
&app.session
|
||||
.partitioning
|
||||
.neighborhood_block(self.id)
|
||||
.neighbourhood_block(self.id)
|
||||
.perimeter,
|
||||
);
|
||||
} else {
|
||||
@ -264,8 +264,8 @@ impl State<App> for SelectBoundary {
|
||||
if let Outcome::Clicked(x) = self.left_panel.event(ctx) {
|
||||
match x.as_ref() {
|
||||
"Cancel" => {
|
||||
// TODO If we destroyed the current neighborhood, then we cancel, we'll pop
|
||||
// back to a different neighborhood than we started with. And also the original
|
||||
// TODO If we destroyed the current neighbourhood, then we cancel, we'll pop
|
||||
// back to a different neighbourhood than we started with. And also the original
|
||||
// partitioning will have been lost!!!
|
||||
app.session.partitioning = self.orig_partitioning.clone();
|
||||
return Transition::Replace(crate::connectivity::Viewer::new_state(
|
||||
@ -323,12 +323,12 @@ impl State<App> for SelectBoundary {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_panel(ctx: &mut EventCtx, app: &App, id: NeighborhoodID, top_panel: &Panel) -> Panel {
|
||||
fn make_panel(ctx: &mut EventCtx, app: &App, id: NeighbourhoodID, top_panel: &Panel) -> Panel {
|
||||
crate::components::LeftPanel::builder(
|
||||
ctx,
|
||||
top_panel,
|
||||
Widget::col(vec![
|
||||
Line("Adjusting neighborhood boundary")
|
||||
Line("Adjusting neighbourhood boundary")
|
||||
.small_heading()
|
||||
.into_widget(ctx),
|
||||
Text::from_all(vec![
|
||||
@ -349,8 +349,8 @@ fn make_panel(ctx: &mut EventCtx, app: &App, id: NeighborhoodID, top_panel: &Pan
|
||||
])
|
||||
.into_widget(ctx),
|
||||
format!(
|
||||
"Neighborhood area: {}",
|
||||
app.session.partitioning.neighborhood_area_km2(id)
|
||||
"Neighbourhood area: {}",
|
||||
app.session.partitioning.neighbourhood_area_km2(id)
|
||||
)
|
||||
.text_widget(ctx),
|
||||
ctx.style()
|
||||
@ -381,12 +381,12 @@ fn make_panel_for_lasso(ctx: &mut EventCtx, top_panel: &Panel) -> Panel {
|
||||
ctx,
|
||||
top_panel,
|
||||
Widget::col(vec![
|
||||
"Draw a custom boundary for a neighborhood"
|
||||
"Draw a custom boundary for a neighbourhood"
|
||||
.text_widget(ctx)
|
||||
.centered_vert(),
|
||||
Text::from_all(vec![
|
||||
Line("Click and drag").fg(ctx.style().text_hotkey_color),
|
||||
Line(" to select the blocks to add to this neighborhood"),
|
||||
Line(" to select the blocks to add to this neighbourhood"),
|
||||
])
|
||||
.into_widget(ctx),
|
||||
]),
|
||||
@ -396,7 +396,7 @@ fn make_panel_for_lasso(ctx: &mut EventCtx, top_panel: &Panel) -> Panel {
|
||||
|
||||
fn help() -> Vec<&'static str> {
|
||||
vec![
|
||||
"You can grow or shrink the blue neighborhood boundary here.",
|
||||
"You can grow or shrink the blue neighbourhood boundary here.",
|
||||
"Due to various known issues, it's not always possible to draw the boundary you want.",
|
||||
"",
|
||||
"The aqua blocks show where you can currently expand the boundary.",
|
||||
|
@ -3,9 +3,9 @@ use map_model::{PathRequest, NORMAL_LANE_THICKNESS};
|
||||
use widgetry::mapspace::ToggleZoomed;
|
||||
use widgetry::{EventCtx, GfxCtx, Key, Line, Outcome, Panel, State, Text, TextExt, Widget};
|
||||
|
||||
use crate::edit::{EditNeighborhood, Tab};
|
||||
use crate::edit::{EditNeighbourhood, Tab};
|
||||
use crate::shortcuts::{find_shortcuts, Shortcuts};
|
||||
use crate::{colors, App, Neighborhood, NeighborhoodID, Transition};
|
||||
use crate::{colors, App, Neighbourhood, NeighbourhoodID, Transition};
|
||||
|
||||
pub struct BrowseShortcuts {
|
||||
top_panel: Panel,
|
||||
@ -14,23 +14,23 @@ pub struct BrowseShortcuts {
|
||||
current_idx: usize,
|
||||
|
||||
draw_path: ToggleZoomed,
|
||||
edit: EditNeighborhood,
|
||||
neighborhood: Neighborhood,
|
||||
edit: EditNeighbourhood,
|
||||
neighbourhood: Neighbourhood,
|
||||
}
|
||||
|
||||
impl BrowseShortcuts {
|
||||
pub fn new_state(
|
||||
ctx: &mut EventCtx,
|
||||
app: &App,
|
||||
id: NeighborhoodID,
|
||||
id: NeighbourhoodID,
|
||||
start_with_request: Option<PathRequest>,
|
||||
) -> Box<dyn State<App>> {
|
||||
let neighborhood = Neighborhood::new(ctx, app, id);
|
||||
let neighbourhood = Neighbourhood::new(ctx, app, id);
|
||||
|
||||
let shortcuts = ctx.loading_screen("find shortcuts", |_, timer| {
|
||||
find_shortcuts(app, &neighborhood, timer)
|
||||
find_shortcuts(app, &neighbourhood, timer)
|
||||
});
|
||||
let edit = EditNeighborhood::new(ctx, app, &neighborhood, &shortcuts);
|
||||
let edit = EditNeighbourhood::new(ctx, app, &neighbourhood, &shortcuts);
|
||||
|
||||
let mut state = BrowseShortcuts {
|
||||
top_panel: crate::components::TopPanel::panel(ctx, app),
|
||||
@ -38,7 +38,7 @@ impl BrowseShortcuts {
|
||||
shortcuts,
|
||||
current_idx: 0,
|
||||
draw_path: ToggleZoomed::empty(ctx),
|
||||
neighborhood,
|
||||
neighbourhood,
|
||||
edit,
|
||||
};
|
||||
|
||||
@ -60,7 +60,7 @@ impl BrowseShortcuts {
|
||||
|
||||
fn recalculate(&mut self, ctx: &mut EventCtx, app: &App) {
|
||||
let (quiet_streets, total_streets) =
|
||||
self.shortcuts.quiet_and_total_streets(&self.neighborhood);
|
||||
self.shortcuts.quiet_and_total_streets(&self.neighbourhood);
|
||||
|
||||
if self.shortcuts.paths.is_empty() {
|
||||
self.left_panel = self
|
||||
@ -104,7 +104,7 @@ impl BrowseShortcuts {
|
||||
))),
|
||||
(quiet_streets as f64) / (total_streets as f64),
|
||||
),
|
||||
"Browse possible shortcuts through the neighborhood.".text_widget(ctx),
|
||||
"Browse possible shortcuts through the neighbourhood.".text_widget(ctx),
|
||||
self.prev_next_controls(ctx),
|
||||
]),
|
||||
)
|
||||
@ -182,7 +182,7 @@ impl State<App> for BrowseShortcuts {
|
||||
ctx,
|
||||
app,
|
||||
x,
|
||||
&self.neighborhood,
|
||||
&self.neighbourhood,
|
||||
&self.left_panel,
|
||||
) {
|
||||
return t;
|
||||
@ -199,7 +199,7 @@ impl State<App> for BrowseShortcuts {
|
||||
current_request,
|
||||
app.session
|
||||
.partitioning
|
||||
.all_blocks_in_neighborhood(self.neighborhood.id),
|
||||
.all_blocks_in_neighbourhood(self.neighbourhood.id),
|
||||
),
|
||||
x,
|
||||
)
|
||||
@ -215,7 +215,7 @@ impl State<App> for BrowseShortcuts {
|
||||
return Transition::Replace(BrowseShortcuts::new_state(
|
||||
ctx,
|
||||
app,
|
||||
self.neighborhood.id,
|
||||
self.neighbourhood.id,
|
||||
Some(current_request),
|
||||
));
|
||||
}
|
||||
@ -230,10 +230,10 @@ impl State<App> for BrowseShortcuts {
|
||||
self.edit.world.draw(g);
|
||||
self.draw_path.draw(g);
|
||||
|
||||
g.redraw(&self.neighborhood.fade_irrelevant);
|
||||
g.redraw(&self.neighbourhood.fade_irrelevant);
|
||||
app.session.draw_all_filters.draw(g);
|
||||
if g.canvas.is_unzoomed() {
|
||||
self.neighborhood.labels.draw(g, app);
|
||||
self.neighbourhood.labels.draw(g, app);
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,13 +243,13 @@ impl State<App> for BrowseShortcuts {
|
||||
} else {
|
||||
Some(self.shortcuts.paths[self.current_idx].get_req().clone())
|
||||
};
|
||||
Self::new_state(ctx, app, self.neighborhood.id, current_request)
|
||||
Self::new_state(ctx, app, self.neighbourhood.id, current_request)
|
||||
}
|
||||
}
|
||||
|
||||
fn help() -> Vec<&'static str> {
|
||||
vec![
|
||||
"This shows every possible path a driver could take through the neighborhood.",
|
||||
"This shows every possible path a driver could take through the neighbourhood.",
|
||||
"Not all paths may be realistic -- small service roads and alleyways are possible, but unlikely.",
|
||||
]
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use map_model::{
|
||||
Pathfinder, Position, RoadID,
|
||||
};
|
||||
|
||||
use crate::{App, Cell, Neighborhood};
|
||||
use crate::{App, Cell, Neighbourhood};
|
||||
|
||||
pub struct Shortcuts {
|
||||
pub paths: Vec<Path>,
|
||||
@ -15,19 +15,19 @@ pub struct Shortcuts {
|
||||
}
|
||||
|
||||
impl Shortcuts {
|
||||
pub fn quiet_and_total_streets(&self, neighborhood: &Neighborhood) -> (usize, usize) {
|
||||
let quiet_streets = neighborhood
|
||||
pub fn quiet_and_total_streets(&self, neighbourhood: &Neighbourhood) -> (usize, usize) {
|
||||
let quiet_streets = neighbourhood
|
||||
.orig_perimeter
|
||||
.interior
|
||||
.iter()
|
||||
.filter(|r| self.count_per_road.get(**r) == 0)
|
||||
.count();
|
||||
let total_streets = neighborhood.orig_perimeter.interior.len();
|
||||
let total_streets = neighbourhood.orig_perimeter.interior.len();
|
||||
(quiet_streets, total_streets)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find_shortcuts(app: &App, neighborhood: &Neighborhood, timer: &mut Timer) -> Shortcuts {
|
||||
pub fn find_shortcuts(app: &App, neighbourhood: &Neighbourhood, timer: &mut Timer) -> Shortcuts {
|
||||
let map = &app.map;
|
||||
let modal_filters = &app.session.modal_filters;
|
||||
// The overall approach: look for all possible paths from an entrance to an exit, only if they
|
||||
@ -39,9 +39,9 @@ pub fn find_shortcuts(app: &App, neighborhood: &Neighborhood, timer: &mut Timer)
|
||||
// to pairs of entrances/exits in the _same_ cell.
|
||||
let mut requests = Vec::new();
|
||||
|
||||
for cell in &neighborhood.cells {
|
||||
let entrances = find_entrances(map, neighborhood, cell);
|
||||
let exits = find_exits(map, neighborhood, cell);
|
||||
for cell in &neighbourhood.cells {
|
||||
let entrances = find_entrances(map, neighbourhood, cell);
|
||||
let exits = find_exits(map, neighbourhood, cell);
|
||||
|
||||
for entrance in &entrances {
|
||||
for exit in &exits {
|
||||
@ -58,12 +58,12 @@ pub fn find_shortcuts(app: &App, neighborhood: &Neighborhood, timer: &mut Timer)
|
||||
|
||||
let mut params = map.routing_params().clone();
|
||||
modal_filters.update_routing_params(&mut params);
|
||||
// Don't allow leaving the neighborhood and using perimeter roads at all. Even if the optimal
|
||||
// Don't allow leaving the neighbourhood and using perimeter roads at all. Even if the optimal
|
||||
// path is to leave and re-enter, don't do that. The point of this view is to show possible
|
||||
// detours people might try to take in response to one filter. Note the original "demand model"
|
||||
// input is bogus anyway; it's all possible entrances and exits to the neighborhood, without
|
||||
// input is bogus anyway; it's all possible entrances and exits to the neighbourhood, without
|
||||
// regards for the larger path somebody actually wants to take.
|
||||
params.avoid_roads.extend(neighborhood.perimeter.clone());
|
||||
params.avoid_roads.extend(neighbourhood.perimeter.clone());
|
||||
|
||||
let pathfinder = Pathfinder::new_dijkstra(map, params, vec![PathConstraints::Car], timer);
|
||||
let paths: Vec<Path> = timer
|
||||
@ -93,12 +93,12 @@ pub fn find_shortcuts(app: &App, neighborhood: &Neighborhood, timer: &mut Timer)
|
||||
for step in path.get_steps() {
|
||||
match step {
|
||||
PathStep::Lane(l) => {
|
||||
if neighborhood.orig_perimeter.interior.contains(&l.road) {
|
||||
if neighbourhood.orig_perimeter.interior.contains(&l.road) {
|
||||
count_per_road.inc(l.road);
|
||||
}
|
||||
}
|
||||
PathStep::Turn(t) => {
|
||||
if neighborhood.interior_intersections.contains(&t.parent) {
|
||||
if neighbourhood.interior_intersections.contains(&t.parent) {
|
||||
count_per_intersection.inc(t.parent);
|
||||
}
|
||||
}
|
||||
@ -122,10 +122,10 @@ struct EntryExit {
|
||||
major_road_name: String,
|
||||
}
|
||||
|
||||
fn find_entrances(map: &Map, neighborhood: &Neighborhood, cell: &Cell) -> Vec<EntryExit> {
|
||||
fn find_entrances(map: &Map, neighbourhood: &Neighbourhood, cell: &Cell) -> Vec<EntryExit> {
|
||||
let mut entrances = Vec::new();
|
||||
for i in &cell.borders {
|
||||
if let Some(major_road_name) = find_major_road_name(map, neighborhood, *i) {
|
||||
if let Some(major_road_name) = find_major_road_name(map, neighbourhood, *i) {
|
||||
let mut seen: HashSet<DirectedRoadID> = HashSet::new();
|
||||
for l in map.get_i(*i).get_outgoing_lanes(map, PathConstraints::Car) {
|
||||
let dr = map.get_l(l).get_directed_parent();
|
||||
@ -142,10 +142,10 @@ fn find_entrances(map: &Map, neighborhood: &Neighborhood, cell: &Cell) -> Vec<En
|
||||
entrances
|
||||
}
|
||||
|
||||
fn find_exits(map: &Map, neighborhood: &Neighborhood, cell: &Cell) -> Vec<EntryExit> {
|
||||
fn find_exits(map: &Map, neighbourhood: &Neighbourhood, cell: &Cell) -> Vec<EntryExit> {
|
||||
let mut exits = Vec::new();
|
||||
for i in &cell.borders {
|
||||
if let Some(major_road_name) = find_major_road_name(map, neighborhood, *i) {
|
||||
if let Some(major_road_name) = find_major_road_name(map, neighbourhood, *i) {
|
||||
let mut seen: HashSet<DirectedRoadID> = HashSet::new();
|
||||
for l in map.get_i(*i).get_incoming_lanes(map, PathConstraints::Car) {
|
||||
let dr = map.get_l(l).get_directed_parent();
|
||||
@ -164,12 +164,12 @@ fn find_exits(map: &Map, neighborhood: &Neighborhood, cell: &Cell) -> Vec<EntryE
|
||||
|
||||
fn find_major_road_name(
|
||||
map: &Map,
|
||||
neighborhood: &Neighborhood,
|
||||
neighbourhood: &Neighbourhood,
|
||||
i: IntersectionID,
|
||||
) -> Option<String> {
|
||||
let mut names = Vec::new();
|
||||
for r in &map.get_i(i).roads {
|
||||
if neighborhood.perimeter.contains(r) {
|
||||
if neighbourhood.perimeter.contains(r) {
|
||||
names.push(map.get_r(*r).get_name(None));
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user