diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt index d6b751149ef..44b6cd2c1af 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/Cycleway.kt @@ -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, @@ -190,6 +189,7 @@ fun getSelectableCycleways( PICTOGRAMS, BUSWAY, SIDEWALK_EXPLICIT, + SIDEWALK_OK, SHOULDER ) val dualCycleways = listOf( diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt index a6415b3a889..1a7c0e0a5a6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreator.kt @@ -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) @@ -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")) { @@ -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" @@ -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" + } PICTOGRAMS -> { tags[cyclewayKey] = "shared_lane" @@ -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") } @@ -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::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) { diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt index 18a112cbca9..6ede17cf6d2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayItem.kt @@ -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 @@ -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 @@ -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 } diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt index d5a8ae393ac..b8afb9a2ab6 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParser.kt @@ -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" -> { @@ -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: @@ -184,6 +191,8 @@ private fun expandRelevantSidesTags(tags: Map): Map { if (tags["bicycle"] !in noCycling) tags["bicycle"] = "no" @@ -33,6 +36,7 @@ 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" @@ -40,8 +44,11 @@ fun SeparateCycleway.applyTo(tags: Tags) { 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 -> { diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayItem.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayItem.kt index 5964dbadac9..480a4b93a2d 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayItem.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayItem.kt @@ -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 @@ -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 diff --git a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParser.kt b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParser.kt index c85e841ede3..ffae8a2aafd 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParser.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParser.kt @@ -25,6 +25,8 @@ fun parseSeparateCycleway(tags: Map): 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" @@ -35,14 +37,13 @@ fun parseSeparateCycleway(tags: Map): 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 diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/CyclewayOverlay.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/CyclewayOverlay.kt index 739cfe6bf72..6f7c887d89b 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/CyclewayOverlay.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/CyclewayOverlay.kt @@ -71,11 +71,10 @@ 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 - + SeparateCycleway.NON_SEGREGATED -> Color.CYAN @@ -83,6 +82,8 @@ private fun SeparateCycleway?.getColor() = when (this) { SeparateCycleway.EXCLUSIVE, SeparateCycleway.EXCLUSIVE_WITH_SIDEWALK -> Color.BLUE + SeparateCycleway.ALLOWED_ON_FOOTWAY -> + Color.AQUAMARINE null -> Color.INVISIBLE @@ -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) @@ -161,4 +162,6 @@ private fun Cycleway?.getStyle(countryInfo: CountryInfo) = when (this) { SEPARATE -> StrokeStyle(Color.INVISIBLE) + SIDEWALK_OK -> + StrokeStyle(Color.CYAN, dashed = true) } diff --git a/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/SeparateCyclewayForm.kt b/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/SeparateCyclewayForm.kt index 4dbb6adb4cc..ea3e14e79e2 100644 --- a/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/SeparateCyclewayForm.kt +++ b/app/src/main/java/de/westnordost/streetcomplete/overlays/cycleway/SeparateCyclewayForm.kt @@ -21,7 +21,7 @@ import org.koin.android.ext.android.inject class SeparateCyclewayForm : AImageSelectOverlayForm() { override val items: List> get() = - listOf(PATH, NON_DESIGNATED_ON_FOOTWAY, NON_SEGREGATED, SEGREGATED, EXCLUSIVE_WITH_SIDEWALK, EXCLUSIVE).map { + SeparateCycleway.entries.map { it.asItem(countryInfo.isLeftHandTraffic) } @@ -75,8 +75,7 @@ class SeparateCyclewayForm : AImageSelectOverlayForm() { 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 = diff --git a/app/src/main/res/drawable/ic_cycleway_sidewalk_ok.xml b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok.xml new file mode 100644 index 00000000000..30e6867a233 --- /dev/null +++ b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_l.xml b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_l.xml new file mode 100644 index 00000000000..e21d2c38e52 --- /dev/null +++ b/app/src/main/res/drawable/ic_cycleway_sidewalk_ok_l.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_separate_cycleway_disallowed.xml b/app/src/main/res/drawable/ic_separate_cycleway_disallowed.xml new file mode 100644 index 00000000000..194c310c1ca --- /dev/null +++ b/app/src/main/res/drawable/ic_separate_cycleway_disallowed.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8e41cd44fcf..0cbe40a7fe1 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -1392,4 +1392,4 @@ Du kannst Informationen zu Spenden auf unserer Website oder Projektseite finden, "Spezifischer Beleuchtungsstatus, der in dieser App nicht wählbar ist" "Ähh…" "Ist hier eine Ampel, die zeigt, wann man die Straße überqueren kann?" - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 416cf0a9954..ffcf2e2bea7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -997,6 +997,7 @@ A level counts as a roof level when its windows are in the roof. Subsequently, r none, but cyclists may use road in both directions none, no cycling allowed in this direction none, but shoulder is usable for cyclists + none, but sign allows cycling on sidewalk Also bicycle path on the other side… No bicycle lane or path at all As usual, tap on one side of the illustration, select a proper answer and do the same for the other side.\n\nNote all the available options: some cases are not obvious, like a oneway road that allows cyclists in both directions! @@ -1673,14 +1674,15 @@ Partially means that a wheelchair can enter and use the restroom, but no handrai It’s a %1$s It’s not a %1$s Bicycle boulevard - No cycling - Cyclists are allowed + + A sign explicitly prohibits cycling + Footway, but a sign allows cycling - Path or trail + Generic path or trail (Not designated for anyone in particular) - Not designated for cyclists (cycling may still be allowed) + Footway, no explicit sign about cycling (whether it is allowed depends on legislation) - Shared-use path + Designated shared-use path Separated bike and foot path diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt index b8961556f9f..76e0f0523f5 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayCreatorKtTest.kt @@ -152,6 +152,64 @@ class CyclewayCreatorKtTest { ) } + @Test fun `apply allowed on sidewalk on one side only`() { + assertEquals( + setOf( + StringMapEntryAdd("cycleway:right", "no"), + StringMapEntryAdd("sidewalk:right:bicycle", "yes"), + StringMapEntryAdd("sidewalk:right:bicycle:signed", "yes") + ), + cycleway(null, SIDEWALK_OK).appliedTo(mapOf()) + ) + assertEquals( + setOf( + StringMapEntryAdd("cycleway:left", "no"), + StringMapEntryAdd("sidewalk:left:bicycle", "yes"), + StringMapEntryAdd("sidewalk:left:bicycle:signed", "yes") + ), + cycleway(SIDEWALK_OK, null).appliedTo(mapOf()) + ) + } + + @Test fun `apply allowed on sidewalk to both sides`() { + assertEquals( + setOf( + StringMapEntryAdd("cycleway:both", "no"), + StringMapEntryAdd("sidewalk:both:bicycle", "yes"), + StringMapEntryAdd("sidewalk:both:bicycle:signed", "yes") + ), + cycleway(SIDEWALK_OK, SIDEWALK_OK).appliedTo(mapOf()) + ) + } + + @Test fun `apply none to a side where SIDEWALK_OK is set`() { + assertEquals( + setOf( + StringMapEntryModify("cycleway:right", "no", "no"), + StringMapEntryDelete("sidewalk:right:bicycle", "yes"), + StringMapEntryDelete("sidewalk:right:bicycle:signed", "yes") + ), + cycleway(null, NONE).appliedTo(mapOf( + "cycleway:right" to "no", + "sidewalk:right:bicycle" to "yes", + "sidewalk:right:bicycle:signed" to "yes" + )) + ) + } + + @Test fun `apply none to a side where sidewalk bicycle designated it set`() { + assertEquals( + setOf( + StringMapEntryModify("cycleway:right", "no", "no"), + StringMapEntryDelete("sidewalk:right:bicycle", "designated"), + ), + cycleway(null, NONE).appliedTo(mapOf( + "cycleway:right" to "no", + "sidewalk:right:bicycle" to "designated", + )) + ) + } + @Test fun `apply dual cycle track answer`() { assertEquals( setOf( diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt index 24691dcc652..c4ca404f9c2 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway/CyclewayParserKtTest.kt @@ -2636,6 +2636,143 @@ class CyclewayParserKtTest { ) } + /* -------------------------------------- sidewalk::bicycle tagging -----------------------------------*/ + + @Test fun `right side sidewalk ok`() { + assertEquals( + cycleway(null, SIDEWALK_OK), + parse( + "sidewalk:right:bicycle" to "yes", + "sidewalk:right:bicycle:signed" to "yes", + "cycleway:right" to "no" + ) + ) + } + + @Test fun `left side sidewalk ok`() { + assertEquals( + cycleway(SIDEWALK_OK, null), + parse( + "sidewalk:left:bicycle" to "yes", + "sidewalk:left:bicycle:signed" to "yes", + "cycleway:left" to "no" + ) + ) + } + + @Test fun `both sides sidewalk ok`() { + assertEquals( + cycleway(SIDEWALK_OK, SIDEWALK_OK), + parse( + "sidewalk:both:bicycle" to "yes", + "sidewalk:both:bicycle:signed" to "yes", + "cycleway:both" to "no" + ) + ) + } + + @Test fun `right side sidewalk ok but without signed yes tag leads to null`() { + assertNull( + parse( + "sidewalk:right:bicycle" to "yes" + ) + ) + assertNull( + parse( + "sidewalk:right:bicycle" to "yes", + "sidewalk:right:bicycle:signed" to "no" + ) + ) + } + + @Test fun `left side sidewalk ok but without signed yes tag leads to null`() { + assertNull( + parse( + "sidewalk:left:bicycle" to "yes" + ) + ) + assertNull( + parse( + "sidewalk:left:bicycle" to "yes", + "sidewalk:left:bicycle:signed" to "no" + ) + ) + } + + @Test fun `both sides sidewalk ok but without signed yes tag leads to null`() { + assertNull( + parse( + "sidewalk:both:bicycle" to "yes" + ) + ) + assertNull( + parse( + "sidewalk:both:bicycle" to "yes", + "sidewalk:both:bicycle:signed" to "no" + ) + ) + } + + @Test fun `right side sidewalk ok cycleway no but without signed yes leads to none`() { + assertEquals( + cycleway(null, NONE), + parse( + "sidewalk:right:bicycle" to "yes", + "cycleway:right" to "no" + ) + ) + } + + @Test fun `left side sidewalk ok cycleway no but without signed yes leads to none`() { + assertEquals( + cycleway(NONE, null), + parse( + "sidewalk:left:bicycle" to "yes", + "cycleway:left" to "no" + ) + ) + } + + @Test fun `both sides sidewalk ok cycleway no but without signed yes leads to none`() { + assertEquals( + cycleway(NONE, NONE), + parse( + "sidewalk:both:bicycle" to "yes", + "cycleway:both" to "no" + ) + ) + } + + @Test fun `right side sidewalk designated leads to sidewalk explicit`() { + assertEquals( + cycleway(null, SIDEWALK_EXPLICIT), + parse( + "sidewalk:right:bicycle" to "designated", + "cycleway:right" to "no" + ) + ) + } + + @Test fun `left side sidewalk designated leads to sidewalk explicit`() { + assertEquals( + cycleway(SIDEWALK_EXPLICIT, null), + parse( + "sidewalk:left:bicycle" to "designated", + "cycleway:left" to "no" + ) + ) + } + + @Test fun `both sides sidewalk designated leads to sidewalk explicit`() { + assertEquals( + cycleway(SIDEWALK_EXPLICIT, SIDEWALK_EXPLICIT), + parse( + "sidewalk:both:bicycle" to "designated", + "cycleway:both" to "no" + ) + ) + } + /* -------------------------------- parse failures -------------------------------------------*/ @Test fun `don't parse opposite-tagging on non oneways`() { diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayCreatorKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayCreatorKtTest.kt index e5babaefdd5..6dbf4a17f40 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayCreatorKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayCreatorKtTest.kt @@ -725,6 +725,79 @@ class SeparateCyclewayCreatorKtTest { )) ) } + + @Test fun `apply allowed on footway changes highway to footway if it isnt already`() { + assertEquals( + setOf( + StringMapEntryAdd("bicycle", "yes"), + StringMapEntryAdd("bicycle:signed", "yes"), + StringMapEntryModify("highway", "cycleway", "footway"), + ), + ALLOWED_ON_FOOTWAY.appliedTo(mapOf("highway" to "cycleway")) + ) + } + + @Test fun `apply path removes bicycle signed tag`() { + assertEquals( + setOf( + StringMapEntryModify("highway", "footway", "path"), + StringMapEntryDelete("bicycle:signed", "yes"), + ), + PATH.appliedTo(mapOf( + "highway" to "footway", + "bicycle" to "yes", + "bicycle:signed" to "yes", + )) + ) + } + + @Test fun `apply non-designated on footway also removes bicycle signed tag`() { + assertEquals( + setOf( + StringMapEntryDelete("bicycle:signed", "yes"), + ), + NON_DESIGNATED_ON_FOOTWAY.appliedTo(mapOf( + "highway" to "footway", + "bicycle:signed" to "yes", + )) + ) + } + + @Test fun `apply allowed on footway adds the bicycle signed tag`() { + assertEquals( + setOf( + StringMapEntryAdd("bicycle:signed", "yes"), + ), + ALLOWED_ON_FOOTWAY.appliedTo(mapOf( + "highway" to "footway", + )) + ) + } + + @Test fun `apply disallowed adds bicycle signed tag`() { + assertEquals( + setOf( + StringMapEntryAdd("bicycle:signed", "yes"), + ), + NOT_ALLOWED.appliedTo(mapOf( + "highway" to "footway", + )) + ) + } + + @Test fun `apply exclusive with sidewalk sets bicycle signed to yes if its currently no`() { + assertEquals( + setOf( + StringMapEntryModify("highway", "path", "cycleway"), + StringMapEntryModify("bicycle:signed", "no", "yes"), + ), + EXCLUSIVE_WITH_SIDEWALK.appliedTo(mapOf( + "highway" to "path", + "bicycle:signed" to "no" + )) + ) + } + } private fun SeparateCycleway.appliedTo(tags: Map): Set { diff --git a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParserKtTest.kt b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParserKtTest.kt index d49d1376ee1..e7da6d9778c 100644 --- a/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParserKtTest.kt +++ b/app/src/test/java/de/westnordost/streetcomplete/osm/cycleway_separate/SeparateCyclewayParserKtTest.kt @@ -30,15 +30,15 @@ class SeparateCyclewayParserKtTest { } @Test fun `parse no bicyclists allowed on path`() { - assertEquals(NOT_ALLOWED, parse("highway" to "path", "bicycle" to "no")) - assertEquals(NOT_ALLOWED, parse("highway" to "footway", "bicycle" to "no")) + assertEquals(NOT_ALLOWED, parse("highway" to "path", "bicycle" to "no", "bicycle:signed" to "yes")) + assertEquals(NOT_ALLOWED, parse("highway" to "footway", "bicycle" to "no", "bicycle:signed" to "yes")) } @Test fun `parse bicyclists allowed on footway`() { - assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "footway", "bicycle" to "yes")) - assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "footway", "bicycle" to "destination")) - assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "path", "foot" to "designated", "bicycle" to "yes")) - assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "path", "foot" to "designated", "bicycle" to "permissive")) + assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "footway", "bicycle" to "yes", "bicycle:signed" to "yes")) + assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "footway", "bicycle" to "destination", "bicycle:signed" to "yes")) + assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "path", "foot" to "designated", "bicycle" to "yes", "bicycle:signed" to "yes")) + assertEquals(ALLOWED_ON_FOOTWAY, parse("highway" to "path", "foot" to "designated", "bicycle" to "permissive", "bicycle:signed" to "yes")) } @Test fun `parse cyclists on non-segregated path`() { @@ -81,4 +81,5 @@ class SeparateCyclewayParserKtTest { } } + private fun parse(vararg pairs: Pair) = parseSeparateCycleway(mapOf(*pairs)) diff --git a/res/graphics/cycleway segregation/disallowed.svg b/res/graphics/cycleway segregation/disallowed.svg new file mode 100644 index 00000000000..486f56df7ed --- /dev/null +++ b/res/graphics/cycleway segregation/disallowed.svg @@ -0,0 +1,77 @@ + + + + + + image/svg+xml + + + + + + + + + + + + diff --git a/res/graphics/cycleway/sidewalk_ok.svg b/res/graphics/cycleway/sidewalk_ok.svg new file mode 100644 index 00000000000..ea631fb380b --- /dev/null +++ b/res/graphics/cycleway/sidewalk_ok.svg @@ -0,0 +1,124 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/res/graphics/cycleway/sidewalk_ok_l.svg b/res/graphics/cycleway/sidewalk_ok_l.svg new file mode 100644 index 00000000000..78c3dc07923 --- /dev/null +++ b/res/graphics/cycleway/sidewalk_ok_l.svg @@ -0,0 +1,139 @@ + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + +