Skip to content

Commit

Permalink
Add "None, but a sign allows cycling on sidewalk" as an option for cy…
Browse files Browse the repository at this point in the history
…cleway overlay/quest (#5575)
  • Loading branch information
wielandb authored Jun 1, 2024
1 parent 39daf2e commit 9d36fff
Show file tree
Hide file tree
Showing 21 changed files with 841 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -113,9 +113,8 @@ enum class Cycleway {
/** cyclists explicitly ought to share the sidewalk with pedestrians, i.e. the cycle track is
* not segregated from the sidewalk */
SIDEWALK_EXPLICIT,
// the following not anymore, see #2276
// no cycleway, but cyclists are allowed on sidewalk
// SIDEWALK_OK,
SIDEWALK_OK,

/** no cycle track or lane */
NONE,
Expand Down Expand Up @@ -190,6 +189,7 @@ fun getSelectableCycleways(
PICTOGRAMS,
BUSWAY,
SIDEWALK_EXPLICIT,
SIDEWALK_OK,
SHOULDER
)
val dualCycleways = listOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ fun LeftAndRightCycleway.applyTo(tags: Tags, isLeftHandTraffic: Boolean) {
tags.expandSides("cycleway", "lane", false)
tags.expandSides("cycleway", "oneway", false)
tags.expandSides("cycleway", "segregated", false)
tags.expandSides("sidewalk", "bicycle", false)
tags.expandSides("sidewalk", "bicycle:signed", false)

applyOnewayNotForCyclists(tags)
left?.applyTo(tags, false, isLeftHandTraffic)
Expand All @@ -39,6 +41,8 @@ fun LeftAndRightCycleway.applyTo(tags: Tags, isLeftHandTraffic: Boolean) {
tags.mergeSides("cycleway", "lane")
tags.mergeSides("cycleway", "oneway")
tags.mergeSides("cycleway", "segregated")
tags.mergeSides("sidewalk", "bicycle")
tags.mergeSides("sidewalk", "bicycle:signed")

// update check date
if (!tags.hasChanges || tags.hasCheckDateForKey("cycleway")) {
Expand Down Expand Up @@ -104,6 +108,7 @@ private fun LeftAndRightCycleway.applyOnewayNotForCyclists(tags: Tags) {
private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHandTraffic: Boolean) {
val side = if (isRight) "right" else "left"
val cyclewayKey = "cycleway:$side"

when (cycleway) {
NONE, NONE_NO_ONEWAY -> {
tags[cyclewayKey] = "no"
Expand Down Expand Up @@ -131,6 +136,7 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
// https://wiki.openstreetmap.org/wiki/File:Z240GemeinsamerGehundRadweg.jpeg
tags[cyclewayKey] = "track"
tags["$cyclewayKey:segregated"] = "no"

Check failure on line 139 in app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt

View workflow job for this annotation

GitHub Actions / Kotlin

Unexpected blank line(s) before "}"
}
PICTOGRAMS -> {
tags[cyclewayKey] = "shared_lane"
Expand All @@ -153,6 +159,11 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
SEPARATE -> {
tags[cyclewayKey] = "separate"
}
SIDEWALK_OK -> {
tags[cyclewayKey] = "no"
tags["sidewalk:$side:bicycle"] = "yes"
tags["sidewalk:$side:bicycle:signed"] = "yes"
}
else -> {
throw IllegalArgumentException("Invalid cycleway")
}
Expand Down Expand Up @@ -185,6 +196,15 @@ private fun CyclewayAndDirection.applyTo(tags: Tags, isRight: Boolean, isLeftHan
if (!touchedSegregatedValue) {
tags.remove("$cyclewayKey:segregated")
}
// no matter what option was chosen, if it's not SIDEWAY_OK, remove that cycling is signed and ok on sidewalk if it's there
if (cycleway != SIDEWALK_OK && tags["sidewalk:$side:bicycle"] == "yes" && tags["sidewalk:$side:bicycle:signed"] == "yes") {
tags.remove("sidewalk:$side:bicycle")
tags.remove("sidewalk:$side:bicycle:signed")
}
// if anything that is not SIDEWALK_EXPLICIT is chosen, remove sidewalk:<side>:bicycle=designated if it's there
if (cycleway != SIDEWALK_EXPLICIT && tags["sidewalk:$side:bicycle"] == "designated") {
tags.remove("sidewalk:$side:bicycle")
}
}

private val Direction.onewayValue get() = when (this) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ private fun Cycleway.getRightHandTrafficIconResId(countryInfo: CountryInfo): Int
NONE_NO_ONEWAY -> R.drawable.ic_cycleway_none_no_oneway
PICTOGRAMS -> countryInfo.pictogramCycleLaneResId
SIDEWALK_EXPLICIT -> R.drawable.ic_cycleway_sidewalk_explicit
SIDEWALK_OK -> R.drawable.ic_cycleway_sidewalk_ok
BUSWAY -> R.drawable.ic_cycleway_bus_lane
SEPARATE -> R.drawable.ic_cycleway_none
SHOULDER -> R.drawable.ic_cycleway_shoulder
Expand All @@ -139,6 +140,7 @@ private fun Cycleway.getLeftHandTrafficIconResId(countryInfo: CountryInfo): Int
NONE_NO_ONEWAY -> R.drawable.ic_cycleway_none_no_oneway_l
PICTOGRAMS -> countryInfo.pictogramCycleLaneMirroredResId
SIDEWALK_EXPLICIT -> R.drawable.ic_cycleway_sidewalk_explicit_l
SIDEWALK_OK -> R.drawable.ic_cycleway_sidewalk_ok_l
BUSWAY -> R.drawable.ic_cycleway_bus_lane_l
SEPARATE -> R.drawable.ic_cycleway_none
SHOULDER -> R.drawable.ic_cycleway_shoulder
Expand Down Expand Up @@ -181,5 +183,6 @@ private fun CyclewayAndDirection.getTitleResId(isContraflowInOneway: Boolean): I
BUSWAY -> R.string.quest_cycleway_value_bus_lane
SEPARATE -> R.string.quest_cycleway_value_separate
SHOULDER -> R.string.quest_cycleway_value_shoulder
SIDEWALK_OK -> R.string.quest_cycleway_value_sidewalk_ok
else -> 0
}
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ private fun parseCyclewayForSide(
val cycleway = tags[cyclewayKey]
val cyclewayLane = tags["$cyclewayKey:lane"]
val isSegregated = tags["$cyclewayKey:segregated"] != "no"
val isCyclingOkOnSidewalk = tags["sidewalk$sideVal:bicycle"] == "yes" && tags["sidewalk$sideVal:bicycle:signed"] == "yes"
val isCyclingDesignatedOnSidewalk = tags["sidewalk$sideVal:bicycle"] == "designated"

val result = when (cycleway) {
"lane", "opposite_lane" -> {
Expand Down Expand Up @@ -122,7 +124,12 @@ private fun parseCyclewayForSide(
if (isSegregated) TRACK else SIDEWALK_EXPLICIT
}
"separate" -> SEPARATE
"no", "opposite" -> NONE
"opposite" -> NONE
"no" -> when {
isCyclingOkOnSidewalk -> SIDEWALK_OK
isCyclingDesignatedOnSidewalk -> SIDEWALK_EXPLICIT
else -> NONE
}
"share_busway", "opposite_share_busway" -> BUSWAY
"shoulder" -> SHOULDER
// values known to be invalid, ambiguous or obsolete:
Expand Down Expand Up @@ -184,6 +191,8 @@ private fun expandRelevantSidesTags(tags: Map<String, String>): Map<String, Stri
result.expandSidesTags("cycleway", "lane", true)
result.expandSidesTags("cycleway", "oneway", true)
result.expandSidesTags("cycleway", "segregated", true)
result.expandSidesTags("sidewalk", "bicycle", true)
result.expandSidesTags("sidewalk", "bicycle:signed", true)
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ fun SeparateCycleway.applyTo(tags: Tags) {
if (tags.containsKey("bicycle") && tags["bicycle"] !in yesButNotDesignated) {
tags["bicycle"] = "yes"
}
if (tags.containsKey("bicycle:signed")) {
tags.remove("bicycle:signed")
}
}
NOT_ALLOWED -> {
if (tags["bicycle"] !in noCycling) tags["bicycle"] = "no"
Expand All @@ -33,15 +36,19 @@ fun SeparateCycleway.applyTo(tags: Tags) {
if (tags["foot"] == "no") {
tags["foot"] = "yes"
}
tags["bicycle:signed"] = "yes"
}
ALLOWED_ON_FOOTWAY, NON_DESIGNATED_ON_FOOTWAY -> {
tags["highway"] = "footway"
if (tags.containsKey("foot")) tags["foot"] = "designated"

if (this == ALLOWED_ON_FOOTWAY) {
if (tags["bicycle"] !in yesButNotDesignated) tags["bicycle"] = "yes"
// add bicycle:signed=yes if not already present
tags["bicycle:signed"] = "yes"
} else {
if (tags["bicycle"] == "designated") tags.remove("bicycle")
tags.remove("bicycle:signed")
}
}
NON_SEGREGATED, SEGREGATED -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ fun SeparateCycleway.asItem(isLeftHandTraffic: Boolean) =

private val SeparateCycleway.titleResId: Int get() = when (this) {
PATH -> R.string.separate_cycleway_path
NOT_ALLOWED -> R.string.separate_cycleway_no
ALLOWED_ON_FOOTWAY -> R.string.separate_cycleway_allowed
NOT_ALLOWED -> R.string.separate_cycleway_no_signed
ALLOWED_ON_FOOTWAY -> R.string.separate_cycleway_footway_allowed_sign
NON_DESIGNATED_ON_FOOTWAY -> R.string.separate_cycleway_no_or_allowed
NON_SEGREGATED -> R.string.separate_cycleway_non_segregated
SEGREGATED -> R.string.separate_cycleway_segregated
Expand All @@ -20,7 +20,7 @@ private val SeparateCycleway.titleResId: Int get() = when (this) {

private fun SeparateCycleway.getIconResId(isLeftHandTraffic: Boolean): Int = when (this) {
PATH -> R.drawable.ic_separate_cycleway_path
NOT_ALLOWED -> R.drawable.ic_separate_cycleway_no
NOT_ALLOWED -> R.drawable.ic_separate_cycleway_disallowed
ALLOWED_ON_FOOTWAY -> R.drawable.ic_separate_cycleway_allowed
NON_DESIGNATED_ON_FOOTWAY -> R.drawable.ic_separate_cycleway_no
NON_SEGREGATED -> R.drawable.ic_separate_cycleway_not_segregated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ fun parseSeparateCycleway(tags: Map<String, String>): SeparateCycleway? {
else -> null // only happens if highway=footway
}

val bicycleSigned = tags["bicycle:signed"] == "yes"

// footway implies foot=designated, path implies foot=yes
val foot = tags["foot"] ?: when (tags["highway"]) {
"footway" -> "designated"
Expand All @@ -35,14 +37,13 @@ fun parseSeparateCycleway(tags: Map<String, String>): SeparateCycleway? {
// invalid tagging: e.g. highway=footway + foot=no
if ((foot == null || foot in noFoot) && (bicycle == null || bicycle in noCycling)) return null

if (bicycle in noCycling) return NOT_ALLOWED
if (bicycle in noCycling && bicycleSigned) return NOT_ALLOWED

if (bicycle in yesButNotDesignated && foot == "designated") return ALLOWED_ON_FOOTWAY
if (bicycle in yesButNotDesignated && bicycleSigned && foot == "designated") return ALLOWED_ON_FOOTWAY

if (bicycle in yesButNotDesignated && foot != "designated") return PATH

if (bicycle != "designated") return NON_DESIGNATED_ON_FOOTWAY

val hasSidewalk = parseSidewalkSides(tags)?.any { it == Sidewalk.YES } == true || tags["sidewalk"] == "yes"
if (hasSidewalk) return EXCLUSIVE_WITH_SIDEWALK

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,18 +71,19 @@ private fun getSeparateCyclewayStyle(element: Element) =

private fun SeparateCycleway?.getColor() = when (this) {
SeparateCycleway.NOT_ALLOWED,
SeparateCycleway.ALLOWED_ON_FOOTWAY,
SeparateCycleway.NON_DESIGNATED_ON_FOOTWAY,
SeparateCycleway.PATH ->
Color.BLACK

Check failure on line 77 in app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/CyclewayOverlay.kt

View workflow job for this annotation

GitHub Actions / Kotlin

Trailing space(s)
SeparateCycleway.NON_SEGREGATED ->
Color.CYAN

SeparateCycleway.SEGREGATED,
SeparateCycleway.EXCLUSIVE,
SeparateCycleway.EXCLUSIVE_WITH_SIDEWALK ->
Color.BLUE
SeparateCycleway.ALLOWED_ON_FOOTWAY ->
Color.AQUAMARINE

null ->
Color.INVISIBLE
Expand Down Expand Up @@ -151,7 +152,7 @@ private fun Cycleway?.getStyle(countryInfo: CountryInfo) = when (this) {
StrokeStyle(Color.LIME, dashed = true)

SIDEWALK_EXPLICIT ->
StrokeStyle(Color.CYAN, dashed = true)
StrokeStyle(Color.CYAN, dashed = false)

NONE ->
StrokeStyle(Color.BLACK)
Expand All @@ -161,4 +162,6 @@ private fun Cycleway?.getStyle(countryInfo: CountryInfo) = when (this) {

SEPARATE ->
StrokeStyle(Color.INVISIBLE)
SIDEWALK_OK ->
StrokeStyle(Color.CYAN, dashed = true)
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import org.koin.android.ext.android.inject
class SeparateCyclewayForm : AImageSelectOverlayForm<SeparateCycleway>() {

override val items: List<DisplayItem<SeparateCycleway>> get() =
listOf(PATH, NON_DESIGNATED_ON_FOOTWAY, NON_SEGREGATED, SEGREGATED, EXCLUSIVE_WITH_SIDEWALK, EXCLUSIVE).map {
SeparateCycleway.entries.map {
it.asItem(countryInfo.isLeftHandTraffic)
}

Expand Down Expand Up @@ -75,8 +75,7 @@ class SeparateCyclewayForm : AImageSelectOverlayForm<SeparateCycleway>() {
prerequisite for it being displayed as a selectable option due to the reasons stated
above.
*/
currentCycleway = if (cycleway == NOT_ALLOWED || cycleway == ALLOWED_ON_FOOTWAY) NON_DESIGNATED_ON_FOOTWAY else cycleway
selectedItem = currentCycleway?.asItem(countryInfo.isLeftHandTraffic)
selectedItem = cycleway?.asItem(countryInfo.isLeftHandTraffic)
}

override fun hasChanges(): Boolean =
Expand Down
53 changes: 53 additions & 0 deletions app/src/main/res/drawable/ic_cycleway_sidewalk_ok.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="256dp"
android:height="256dp"
android:viewportWidth="256"
android:viewportHeight="256">
<path
android:pathData="M128,0.01l72,0l0,256l-72,0z"
android:fillColor="#aaa"/>
<path
android:pathData="m192,231.99l8,0m-8,-24l8,0m-8,-24l8,0m0,-24l-8,0m0,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-8,-24l8,0m-72,216l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m16,120l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m-32,132l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-48l16,0m-16,-24l16,0m-16,-48l16,0m-16,24l16,0m-16,72l16,0m32,156l0,-256m-16,0l0,256m-16,0l0,-256m-16,0l0,256m32,-228.58l16,0m-16,24l16,0m-16,192l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-24l16,0m-16,-72l16,0"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#999"/>
<path
android:pathData="M120,0.01l8,0l0,256l-8,0z"
android:fillColor="#999"/>
<path
android:pathData="M200,0.01l8,0l0,256l-8,0z"
android:fillColor="#999"/>
<path
android:pathData="M112,0.01l8,0l0,256l-8,0z"
android:fillColor="#666"/>
<path
android:pathData="m168.11,92.45c-2.87,0 -5.19,2.32 -5.19,5.19 0,2.87 2.33,5.19 5.19,5.19 2.87,0 5.19,-2.32 5.19,-5.19 0,-2.87 -2.32,-5.19 -5.19,-5.19zM162.68,104.72c-5.25,1.96 -9.3,5.3 -9.35,10.86 0,1.42 1.15,2.57 2.57,2.57 1.42,0 2.57,-1.15 2.57,-2.57 0.04,-1.42 0.64,-2.62 1.52,-3.59 0.03,1.26 0.23,2.68 0.66,4.26 -0.27,0.45 -0.38,1.12 -0.27,2.1 -0.91,5.23 -6.67,8.63 -10.39,10.32 -1.3,0.58 -1.89,2.1 -1.31,3.4 0.58,1.3 2.1,1.89 3.4,1.31 4.93,-2.57 11.2,-6.71 13,-12.15 4.1,1.91 7.93,6.2 8.62,10.16 0.2,1.41 1.51,2.38 2.91,2.18 1.41,-0.2 2.39,-1.5 2.18,-2.91 -1.07,-6.35 -5.45,-11.05 -10.93,-13.81 -1.58,-0.68 -1.71,-4.38 -1.62,-5.71 4.8,2.73 9.72,3.94 14.59,1.55 1.27,-0.64 1.79,-2.18 1.15,-3.45 -0.64,-1.27 -2.18,-1.79 -3.45,-1.15 -5.72,2.83 -9.29,-1.5 -12.3,-3.26 -1.08,-0.75 -2.51,-0.76 -3.58,-0.1z"
android:strokeWidth="3.78"
android:fillColor="#ffffff"
android:strokeColor="#00000000"/>
<path
android:pathData="m153.73,140.93l22.41,0a4.8,4.8 0,0 1,4.8 4.79l0,20.41a4.8,4.8 0,0 1,-4.8 4.79l-22.41,0a4.8,4.8 0,0 1,-4.8 -4.79l0,-20.41a4.8,4.8 0,0 1,4.8 -4.79z"
android:strokeLineJoin="round"
android:strokeWidth="2"
android:fillColor="#ffffff"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m162.06,155.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM177.24,155.14a4.64,4.66 90,0 1,-4.66 4.64,4.64 4.66,90 0,1 -4.66,-4.64 4.64,4.66 90,0 1,4.66 -4.64,4.64 4.66,90 0,1 4.66,4.64zM159.99,147.99 L165.88,155.11l6.73,0L167.99,147.99L160,147.99m5.89,7.12 l2.11,-8.8m0,0 l2.05,-0.01m-12.57,8.8 l3.51,-10.39l3.36,0"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m164.94,164.93c0,1.5 -0.99,3 -3,3 -2.01,0 -3.03,-1.47 -3,-3 0.03,-1.52 0.95,-3 3,-3 2.05,0 3,1.5 3,3zM163.44,164.93c0,-0.73 -0.5,-1.5 -1.5,-1.5 -1.01,0 -1.5,0.74 -1.5,1.5 0,0.76 0.5,1.5 1.5,1.5 1,0 1.5,-0.77 1.5,-1.5z"
android:strokeWidth="0.5"
android:fillColor="#000000"/>
<path
android:pathData="m171.43,167.94l-1.64,0l-1.85,-2.5 -0.5,0.5l0,2l-1.5,0l0,-6l1.5,0l0,2.5l2.5,-2.5l1.65,0l-2.65,2.6z"
android:strokeWidth="0.5"
android:fillColor="#000000"/>
<path
android:pathData="M0,0h112v256h-112z"
android:fillColor="#808080"/>
</vector>
Loading

0 comments on commit 9d36fff

Please sign in to comment.