Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Start importing all footways and shared-use paths! #87, #77 #90

Merged
merged 3 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 7 additions & 8 deletions import_streets/src/extract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,14 @@ impl OsmExtract {
return false;
}

// If we're only handling sidewalks tagged on roads, skip a bunch of ways
// If we're only handling sidewalks tagged on roads, skip crossings and separate sidewalks
// Note we have to do this here -- get_lane_specs_ltr doesn't support decisions like
// "actually, let's pretend this road doesn't exist at all"
if opts.map_config.inferred_sidewalks {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

So this experiment now just means "bring in highway=footway, footway={sidewalk,crossing} ways". All of the new stuff in this PR works both with and without this experiment

if tags.is_any(
osm::HIGHWAY,
vec!["footway", "path", "pedestrian", "steps", "track"],
) {
if !tags.is_any("bicycle", vec!["designated", "yes"]) {
return false;
}
if tags.is(osm::HIGHWAY, "footway")
&& tags.is_any("footway", vec!["crossing", "sidewalk"])
{
return false;
}
}

Expand Down
9 changes: 9 additions & 0 deletions osm2streets/src/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,15 @@ impl From<&LaneSpec> for RoadPart {
carriage: Carriage::Cars,
},
LaneType::Construction => Designation::NoTravel,
LaneType::Footway => Designation::Travel {
carriage: Carriage::Foot,
direction: TrafficDirections::BothWays,
},
LaneType::SharedUse => Designation::Travel {
// TODO Both foot and bike
carriage: Carriage::Foot,
direction: TrafficDirections::BothWays,
},
},
can_enter_from_inside: true,
can_enter_from_outside: false,
Expand Down
50 changes: 31 additions & 19 deletions street_network/src/lanes/classic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ pub fn get_lane_specs_ltr(tags: &Tags, cfg: &MapConfig) -> Vec<LaneSpec> {

// TODO This hides a potentially expensive (on a hot-path) clone
let mut tags = tags.clone();
// This'll do weird things for the special cases of railways and cycleways/footways, but the
// added tags will be ignored, so it doesn't matter too much. Running this later causes borrow
// checker problems.
infer_sidewalk_tags(&mut tags, cfg);

let fwd = |lt: LaneType| LaneSpec {
Expand All @@ -31,33 +34,20 @@ pub fn get_lane_specs_ltr(tags: &Tags, cfg: &MapConfig) -> Vec<LaneSpec> {
if tags.is_any("railway", vec!["light_rail", "rail"]) {
return vec![fwd(LaneType::LightRail)];
}
if tags.is(osm::HIGHWAY, "steps") {
return vec![fwd(LaneType::Sidewalk)];
}
// Eventually, we should have some kind of special LaneType for shared walking/cycling paths of
// different kinds. Until then, model by making bike lanes and a shoulder for walking.
if tags.is_any(
osm::HIGHWAY,
vec!["cycleway", "footway", "path", "pedestrian", "track"],
) {
// If it just allows foot traffic, simply make it a sidewalk. For most of the above highway
// types, assume bikes are allowed, except for footways, where they must be explicitly
// allowed.
if tags.is("bicycle", "no")
|| (tags.is(osm::HIGHWAY, "footway")
&& !tags.is_any("bicycle", vec!["designated", "yes", "dismount"]))
{
return vec![fwd(LaneType::Sidewalk)];
}
// Otherwise, there'll always be a bike lane.

// If it's a primarily cycleway, have directional bike lanes and add a shoulder for walking
Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's still odd to use two directional lanes for this case. I think street_network::Direction and everything downstream needs to have a both-ways case. It's a very complex change downstream, affecting routing, rendering, traffic simulation... so not attempting it yet.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes, I agree. An "alternates" case too.

Most reads of the value will boil down to "is travel supported in this direction", right? Routing could implement "alternating" and use that for "both ways" too.

I called it TravelDirection in my types.

// TODO Consider variations of SharedUse that specify the priority is cyclists over pedestrians
// in this case?
if tags.is(osm::HIGHWAY, "cycleway") {
let mut fwd_side = vec![fwd(LaneType::Biking)];
let mut back_side = if tags.is("oneway", "yes") {
vec![]
} else {
vec![back(LaneType::Biking)]
};

// TODO If this cycleway is parallel to a main road, we might end up with double sidewalks.
// Once snapping works well, this problem will improve
if !tags.is("foot", "no") {
fwd_side.push(fwd(LaneType::Shoulder));
if !back_side.is_empty() {
Expand All @@ -67,6 +57,28 @@ pub fn get_lane_specs_ltr(tags: &Tags, cfg: &MapConfig) -> Vec<LaneSpec> {
return LaneSpec::assemble_ltr(fwd_side, back_side, cfg.driving_side);
}

// These roads will only exist if cfg.inferred_sidewalks is false
if tags.is(osm::HIGHWAY, "footway") && tags.is_any("footway", vec!["crossing", "sidewalk"]) {
// Treating a crossing as a sidewalk for now. Eventually crossings need to be dealt with
// completely differently.
return vec![fwd(LaneType::Sidewalk)];
}

// Handle pedestrian-oriented spaces
if tags.is_any(
osm::HIGHWAY,
vec!["footway", "path", "pedestrian", "steps", "track"],
) {
// Assume no bikes unless they're explicitly allowed
if tags.is_any("bicycle", vec!["designated", "yes", "dismount"]) {
return vec![fwd(LaneType::SharedUse)];
}

return vec![fwd(LaneType::Footway)];
}

// Most cases are below -- it's a "normal road"

// TODO Reversible roads should be handled differently?
let oneway =
tags.is_any("oneway", vec!["yes", "reversible"]) || tags.is("junction", "roundabout");
Expand Down
28 changes: 26 additions & 2 deletions street_network/src/lanes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ pub enum LaneType {
Construction,
LightRail,
Buffer(BufferType),
/// Some kind of pedestrian-only path unassociated with a road
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Very related to #77. I'm not happy with this classification as "only pedestrians" and "both pedestrians and cyclists." Things like living streets or alleyways, where motor vehicles are also allowed, aren't captured yet. Maybe we just need one shared use lane type, with a way to specify the legal or cultural priority between vehicles. For instance, I know many streets in the US without pavement, where it's effectively shared use and motor vehicles get priority.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think when it comes down to it, every lane is a list of specific designations and restrictions. There are also classifications, informed by the locale, I suppose, like "only pedestrians", "bike lane" or "bus lane (bikes allowed)" that inform markup decisions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I like this way of thinking about it. Going to start a fresh issue on this soon to consolidate all of the thoughts around this...

Footway,
/// Some kind of shared pedestrian+bicycle space. May be associated with a road or not. Unclear
/// which mode has effective priority.
SharedUse,
}

#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord, Serialize, Deserialize)]
Expand Down Expand Up @@ -61,6 +66,8 @@ impl LaneType {
LaneType::Construction => false,
LaneType::LightRail => true,
LaneType::Buffer(_) => false,
LaneType::Footway => false,
LaneType::SharedUse => true,
}
}

Expand All @@ -76,17 +83,22 @@ impl LaneType {
LaneType::Construction => false,
LaneType::LightRail => true,
LaneType::Buffer(_) => false,
LaneType::Footway => true,
LaneType::SharedUse => true,
}
}

pub fn is_walkable(self) -> bool {
self == LaneType::Sidewalk || self == LaneType::Shoulder
matches!(
self,
LaneType::Sidewalk | LaneType::Shoulder | LaneType::Footway | LaneType::SharedUse
)
}

pub fn describe(self) -> &'static str {
match self {
LaneType::Driving => "a general-purpose driving lane",
LaneType::Biking => "a protected bike lane",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

"protected" never ever made sense, I erroneously wrote this long ago.

Also this is a localization issue... "cycling lane" or "cycle lane" would fit better in the UK. But anyway.

LaneType::Biking => "a bike lane",
LaneType::Bus => "a bus-only lane",
LaneType::Parking => "an on-street parking lane",
LaneType::Sidewalk => "a sidewalk",
Expand All @@ -99,6 +111,8 @@ impl LaneType {
LaneType::Buffer(BufferType::Planters) => "planter barriers",
LaneType::Buffer(BufferType::JerseyBarrier) => "a Jersey barrier",
LaneType::Buffer(BufferType::Curb) => "a raised curb",
LaneType::Footway => "a footway",
LaneType::SharedUse => "a shared-use walking/cycling path",
}
}

Expand All @@ -118,6 +132,8 @@ impl LaneType {
LaneType::Buffer(BufferType::Planters) => "planters",
LaneType::Buffer(BufferType::JerseyBarrier) => "Jersey barrier",
LaneType::Buffer(BufferType::Curb) => "curb",
LaneType::Footway => "footway",
LaneType::SharedUse => "shared-use path",
}
}

Expand All @@ -137,6 +153,8 @@ impl LaneType {
"planters" => Some(LaneType::Buffer(BufferType::Planters)),
"Jersey barrier" => Some(LaneType::Buffer(BufferType::JerseyBarrier)),
"curb" => Some(LaneType::Buffer(BufferType::Curb)),
"footway" => Some(LaneType::Footway),
"shared-use path" => Some(LaneType::SharedUse),
_ => None,
}
}
Expand All @@ -154,6 +172,8 @@ impl LaneType {
LaneType::Construction => 'x',
LaneType::LightRail => 'l',
LaneType::Buffer(_) => '|',
LaneType::Footway => 'f',
LaneType::SharedUse => 'V',
dabreegster marked this conversation as resolved.
Show resolved Hide resolved
}
}

Expand All @@ -170,6 +190,8 @@ impl LaneType {
'x' => LaneType::Construction,
'l' => LaneType::LightRail,
'|' => LaneType::Buffer(BufferType::FlexPosts),
'f' => LaneType::Footway,
'V' => LaneType::SharedUse,
_ => panic!("from_char({}) undefined", x),
}
}
Expand Down Expand Up @@ -249,6 +271,8 @@ impl LaneSpec {
vec![(Distance::meters(1.5), "default")]
}
LaneType::Buffer(BufferType::Curb) => vec![(Distance::meters(0.5), "default")],
LaneType::Footway => vec![(Distance::meters(2.0), "default")],
LaneType::SharedUse => vec![(Distance::meters(3.0), "default")],
}
}

Expand Down
2 changes: 2 additions & 0 deletions street_network/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,8 @@ impl Road {
self.osm_tags.is_any(
osm::HIGHWAY,
vec![
// TODO cycleway in here is weird, reconsider. is_footway is only used in one
// disabled transformation right now.
"cycleway",
"footway",
"path",
Expand Down
5 changes: 2 additions & 3 deletions street_network/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,8 @@ pub struct MapConfig {
/// (Australia).
pub driving_side: DrivingSide,
pub bikes_can_use_bus_lanes: bool,
/// If true, roads without explicitly tagged sidewalks may have sidewalks or shoulders. If
/// false, no sidewalks will be inferred if not tagged in OSM, and separate sidewalks will be
/// included.
/// If true, roads without explicitly tagged sidewalks may be assigned sidewalks or shoulders. If
/// false, no inference will occur and separate sidewalks and crossings will be included.
pub inferred_sidewalks: bool,
/// Street parking is divided into spots of this length. 8 meters is a reasonable default, but
/// people in some regions might be more accustomed to squeezing into smaller spaces. This
Expand Down
19 changes: 19 additions & 0 deletions tests-web/www/js/layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,25 @@ export const makeLanePolygonLayer = (text) => {

return new L.geoJSON(JSON.parse(text), {
style: function (feature) {
if (feature.properties.type == "Footway") {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The styling is not great. Any ideas for distinguishing the two?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe it's time to start copying design from the strassenraumkarte-neukoelln :P

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I already lifted the dashed outline idea and path color from there ;)

return {
fill: true,
fillColor: "#DDDDE8",
stroke: true,
color: "black",
dashArray: "5,10",
};
}
if (feature.properties.type == "SharedUse") {
return {
fill: true,
fillColor: "#E5E1BB",
stroke: true,
color: "black",
dashArray: "5,10",
};
}

return {
fill: true,
fillColor: colors[feature.properties.type] || "red",
Expand Down
Loading