Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ import de.westnordost.streetcomplete.quests.drinking_water_type.AddDrinkingWater
import de.westnordost.streetcomplete.quests.existence.CheckExistence
import de.westnordost.streetcomplete.quests.ferry.AddFerryAccessMotorVehicle
import de.westnordost.streetcomplete.quests.ferry.AddFerryAccessPedestrian
import de.westnordost.streetcomplete.quests.ferry.AddFerryFee
import de.westnordost.streetcomplete.quests.fire_hydrant.AddFireHydrantType
import de.westnordost.streetcomplete.quests.fire_hydrant_diameter.AddFireHydrantDiameter
import de.westnordost.streetcomplete.quests.fire_hydrant_position.AddFireHydrantPosition
Expand Down Expand Up @@ -438,6 +439,7 @@ fun questTypeRegistry(
// ferry: usually visible from looking at the boat, but not always...
101 to AddFerryAccessPedestrian(),
102 to AddFerryAccessMotorVehicle(),
185 to AddFerryFee(),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this ordinal is already taken


103 to AddProhibitedForPedestrians(), // need to understand the pedestrian situation

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package de.westnordost.streetcomplete.quests.ferry

import de.westnordost.streetcomplete.R
import de.westnordost.streetcomplete.data.elementfilter.toElementFilterExpression
import de.westnordost.streetcomplete.data.osm.geometry.ElementGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Way
import de.westnordost.streetcomplete.data.osm.mapdata.filter
import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType
import de.westnordost.streetcomplete.data.quest.AndroidQuest
import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CAR
import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.RARE
import de.westnordost.streetcomplete.osm.Tags
import de.westnordost.streetcomplete.quests.YesNoQuestForm
import de.westnordost.streetcomplete.util.ktx.toYesNo

class AddFerryFee : OsmElementQuestType<Boolean>, AndroidQuest {

private val filter by lazy {
("""ways, relations with
route = ferry
and !fee
and !toll"""
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You could add the other values:

and !toll:bicycle
and !toll:hgv
and !toll:conditional

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

or better a wildcard

Copy link
Copy Markdown
Member

@westnordost westnordost Feb 11, 2026

Choose a reason for hiding this comment

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

on the other hand, if any fee:.* or toll:.* is defined, I think it would still be missing data when fee/toll is not defined. So, isn't it fine to ask for fee then?

Hm.

Well, if we have fee:hgv=yes and otherwise nothing, it should probably be interpreted as "no fee for anyone but HGV".

But then, tagging fee=yes would be wrong. The question is, however, "Is there a fee for anyone to use this ferry?"

So, that's maybe the reason why we don't want to have any fee:.* at all in order to be able to ask this question?

The nature of the question is still a problem though, then. If the ferry costs only for motor vehicles, fee=yes alone would be wrong. It would need to be fee=no + fee:motor_vehicle=yes. The question however assumes that =yes should be tagged if anyone needs to pay a fee. So, maybe the quest itself just doesn't work out.

Copy link
Copy Markdown
Member

@mnalis mnalis Feb 11, 2026

Choose a reason for hiding this comment

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

TL;DR: the viable options for this quest (that I see) are:

  • Ask something like "Who must pay to use this ferry?", with answers "Everyone" (tags fee=yes), "Nobody" (tags fee=no) and "Some classes of travelers" (tags documented fee=unknown, or maybe better asks user to create a note with details / url / picture of the pricelist). I would skip anything with fee:* or toll:* already tagged in this case. Or,
  • instead have multiple quests1 which are simple yes/no answers, but ask separately for each category (motorcar, pedestrian, hgv, bicycle...) and thus we tag all the details right from the start. Better value, but more work.

Well, if we have fee:hgv=yes and otherwise nothing, it should probably be interpreted as "no fee for anyone but HGV".

Well, maybe, but not really - similar issue was discussed in #3219 (e.g. just because someone tagged only payment:cash=yes + payment:visa=yes does not necessarily imply payment:jcb=no -- it also likely that the data is partial, and the mapper did not have that card to check it)

Similarly, if you wen with car you might add payment:motorcar=yes (or =no), but have not checked whether HGV, BUS and other classes must pay. Or you might have seen the pricelist, but it was too complex for you to tag (e.g. only HGV above certain weight or size must pay etc.)

So, that's maybe the reason why we don't want to have any fee:.* at all in order to be able to ask this question?

I think the main reason is to avoid extreme complexity. If some fee:* is tagged, it means somebody (who by definition was better equipped than this SC Quest to tag this) has already surveyed it, and since the SC quest as proposed is veeery simple, it would be more likely to damage the data then improve it in such cases.

Thus, it's better to skip such cases, and only add data where it is completely missing - as some (even undetailed) information is better than none, and we avoid 95%+ of the complexity.

If the ferry costs only for motor vehicles, fee=yes alone would be wrong.

Hm, yeah... To me it seems clear that fee=no means "nobody has to pay anything ever, there isn't even equipment/personnel for paying".

However fee=yes does not seem to mean the inverse of no (i.e. "somebody has to pay in at least some cases") according to the wiki - but instead "A fee is generally charged" which I interpret to mean "vast majority of users have to pay"; and that answer would not work for things like "cars have to pay but pedestrians are free" ferry where there is no "vast majority" of either.

Wiki also mentions fee=unknown which means exact details of who and when has to pay are not mapped (and should be resurveyed via fee:conditional, fee:hgv and similar tags); but its information is not much better than no value being tagged (but may be useful for SC anyway as a way to skip that quest, i.e. similar to *:signed=no usages)

Footnotes

  1. or single quest which ask slightly different questions, and tags different answers.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm definitely too confused now by the overlap between the two PRs (#6726 and #6556). Should I just close mine so that the feedback stays in one place?

From practical mapping experience, especially in Alpine regions, toll=yes is commonly used even when only certain categories of users have to pay. Ah, and I'm going to go with toll=* now, because that was voted the winner in the forum btw.

Some example:

  • Silvretta Hochalpenstraße (way/229835574) has toll=yes, although pedestrians and cyclists are free, and even some public transport buses are free but tourist buses not.
  • Kaunertaler Gletscherstraße (way/747876761) is tagged toll=yes under similar real-world conditions.
  • Ötztaler Gletscherstraße (way/46613597) uses toll=yes with explicit exemptions (toll:bicycle=no, toll:foot=no).
  • Timmelsjoch (way/28048067) has toll=yes and toll:bicycle=no, but no toll:foot=no, even though pedestrians do not pay.

In practice, toll=yes is widely used to indicate that some category of road user must pay, not necessarily all users. It does not seem to be interpreted as “everyone must pay”, but rather as “this road/ferry is toll-operated”.

Based on this real-world tagging practice, I would argue that using toll=yes for ferries where some vehicle classes must pay is consistent with existing OSM usage. From my experience with ferries so far, I personally don't know of any ferry where you can travel for free on any form of transport (well, the Lake Constance ferry is operated by the municipal utilities, just like the local public transport system).
The question is therefore: How large is the proportion of ferries on which only certain modes of transport pay, but not all?

That said, the discussion highlights a legitimate semantic ambiguity. Two possible future improvements could be:

  1. Add follow-up quests that explicitly ask whether pedestrians or cyclists must pay.
  2. Replace the simple Yes/No question with a multi-select approach (similar to the recycling quest), allowing users to select which user groups must pay. (I did this also with the SCEE guidepost quest)
  3. Leave the quest unchanged as it is now.

For now, this quest intentionally captures only the basic information (toll=yes / toll=no) and could skip objects where more detailed toll:* or fee:* tagging already exists, to avoid overwriting more specific data.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

personally don't know of any ferry where you can travel for free on any form of transport

Ferries that cross the Nord-Ostsee-Kanal are free

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

personally don't know of any ferry where you can travel for free on any form of transport

River crossing ferries in Poland are often free, for multiple reasons (can elaborate if relevant).

).toElementFilterExpression()
}
override val changesetComment = "Specify ferry fee"
override val wikiLink = "Tag:route=ferry"
override val icon = R.drawable.ic_quest_ferry_fee
override val hasMarkersAtEnds = true
override val achievements = listOf(RARE)

override fun getTitle(tags: Map<String, String>) = R.string.quest_ferry_fee_title

override fun createForm() = YesNoQuestForm()

override fun applyAnswerTo(answer: Boolean, tags: Tags, geometry: ElementGeometry, timestampEdited: Long) {
tags["fee"] = answer.toYesNo()
}

override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable<Element> {
val wayIdsInFerryRoutes = wayIdsInFerryRoutes(mapData.relations)
return mapData
.filter(filter)
.filter { it !is Way || it.id !in wayIdsInFerryRoutes }
.asIterable()
}

override fun isApplicableTo(element: Element): Boolean? {
if (!filter.matches(element)) return false
if (element is Way) return null
return true
}
}
1 change: 1 addition & 0 deletions app/src/androidMain/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1121,6 +1121,7 @@ A level counts as a roof level when its windows are in the roof. Subsequently, r
<string name="quest_ferry_pedestrian_title">Does this ferry route transport pedestrians?</string>

<string name="quest_ferry_motor_vehicle_title">Does this ferry route transport motor vehicles?</string>
<string name="quest_ferry_fee_title">Is there a fee for anyone to use this ferry?</string>

<string name="quest_fireHydrant_diameter_title">What diameter is specified on the sign for this fire hydrant?</string>
<string name="quest_fireHydrant_diameter_unusualInput_confirmation_description2">This diameter looks implausible, it is usually between %1$d and %2$d.</string>
Expand Down
53 changes: 53 additions & 0 deletions app/src/main/res/drawable/ic_quest_ferry_fee.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="128dp"
android:height="128dp"
android:viewportWidth="128"
android:viewportHeight="128">
<path
android:pathData="m119.42,96c-17.67,30.6 -56.81,41.09 -87.41,23.42 -30.61,-17.67 -41.09,-56.81 -23.42,-87.41 17.67,-30.6 56.81,-41.09 87.41,-23.42 30.6,17.67 41.09,56.81 23.42,87.41"
android:strokeWidth=".19997"
android:fillColor="#9bbe55"/>
<path
android:fillColor="#FF000000"
android:pathData="m30.39,39.98c-0.87,0 -1.58,0.35 -1.58,0.79v1.58c0,0.42 0.66,0.75 1.48,0.78l-2.74,18.92h2.84c0.87,0 1.58,0.71 1.58,1.58v4.18h-21.91c2.16,7.45 8,16 8,16 1.59,2.43 3.73,4.87 5.21,6.23 15.77,14.48 88.77,5.77 88.77,5.77s1.58,-5.33 3,-12c0,0 3,-11 3,-16h-19.88v-4.18c0,-0.87 0.71,-1.58 1.58,-1.58h5.99l-1.26,-6.3h-50.44v-12.61c0.87,0 1.58,-0.35 1.58,-0.79v-1.58c0,-0.44 -0.71,-0.79 -1.58,-0.79z"
android:fillAlpha="0.2"/>
<path
android:pathData="m17.99,79.96c1.59,2.43 3.74,4.87 5.21,6.23 15.77,14.48 88.77,5.77 88.77,5.77s1.57,-5.33 3,-12z"
android:fillColor="#dd2e44"/>
<path
android:pathData="m49.28,23.53c-0.87,0 -1.58,0.71 -1.58,1.58l0,12.61c0,0.34 0.11,0.66 0.3,0.92l-3.45,0l0,-7.22c0,-0.87 -0.71,-1.58 -1.58,-1.58 -0.87,0 -1.58,0.71 -1.58,1.58l0,7.22l-11.03,0l-2.84,19.57l2.84,0c0.87,0 1.58,0.71 1.58,1.58l0,4.18l-21.91,0c2.16,7.45 8,16 8,16l96.99,0s3,-11 3,-16l-19.88,0l0,-4.18c0,-0.87 0.71,-1.58 1.58,-1.58l5.99,0l-1.26,-6.3l-50.44,0l0,-13.27l-3.46,0c0.19,-0.26 0.3,-0.58 0.3,-0.92l0,-12.61c0,-0.87 -0.71,-1.58 -1.58,-1.58z"
android:fillColor="#ccd6dd"/>
<path
android:pathData="m29.89,42.44 l-0.95,6.3l12.45,0l0,-6.3zM44.55,42.44l0,6.3l4.73,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM36.67,58.2c-0.87,0 -1.58,0.71 -1.58,1.58l0,4.18l15.76,0l0,-4.18c0,-0.87 -0.71,-1.58 -1.58,-1.58zM58.73,58.2c-0.87,0 -1.58,0.71 -1.58,1.58l0,4.18l15.76,0l0,-4.18c0,-0.87 -0.71,-1.58 -1.58,-1.58zM80.8,58.2c-0.87,0 -1.58,0.71 -1.58,1.58l0,4.18l15.76,0l0,-4.18c0,-0.87 -0.71,-1.58 -1.58,-1.58zM27.21,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM39.82,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM52.42,70.81c-0.87,0 -1.57,0.71 -1.57,1.58l0,3.15c0,0.87 0.7,1.58 1.57,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM65.03,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM77.64,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM90.25,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58zM102.86,70.81c-0.87,0 -1.58,0.71 -1.58,1.58l0,3.15c0,0.87 0.71,1.58 1.58,1.58l3.15,0c0.87,0 1.58,-0.71 1.58,-1.58l0,-3.15c0,-0.87 -0.71,-1.58 -1.58,-1.58z"
android:fillColor="#66757f"/>
<path
android:pathData="m55.59,38.51c0,0.44 -0.71,0.79 -1.58,0.79l-23.64,0c-0.87,0 -1.58,-0.35 -1.58,-0.79l0,-1.58c0,-0.44 0.71,-0.79 1.58,-0.79l23.64,0c0.87,0 1.58,0.35 1.58,0.79z"
android:fillColor="#269"/>
<path
android:pathData="m4.51,88.38c5.16,12.79 14.45,24.03 27.33,31.46 7.76,4.48 16.07,7.14 24.44,8.13h15.08c19.35,-2.3 37.41,-13.38 47.9,-31.56 1.51,-2.62 2.81,-5.31 3.91,-8.04 -10.01,-1.97 -19.73,-1.58 -29.66,0 -10.14,1.61 -21.61,2.17 -29.66,0 -17.97,-4.84 -22.9,1.94 -29.66,0 -5.66,-1.63 -14.6,-9.32 -17.14,-0.43z"
android:strokeWidth=".19997"
android:fillColor="#3e74c9"/>
<path
android:fillColor="#FF000000"
android:pathData="m21.74,88.09c-1.04,0.03 -1.97,0.35 -2.76,1.07 1.48,2.07 3.22,4.05 4.49,5.22 15.77,14.48 88.77,5.77 88.77,5.77s1.05,-3.63 2.22,-8.56c-6.93,-0.47 -13.79,0.04 -20.75,1.14 -10.14,1.61 -21.61,2.17 -29.66,0 -17.97,-4.84 -22.9,1.94 -29.66,0 -3.71,-1.07 -8.84,-4.75 -12.64,-4.64z"
android:fillAlpha="0.13333"/>
<path
android:pathData="m86.49,79.25a19.45,19.48 0,0 0,-19.45 19.48,19.45 19.48,0 0,0 19.45,19.48 19.45,19.48 0,0 0,19.45 -19.48,19.45 19.48,0 0,0 -19.45,-19.48z"
android:strokeAlpha="0.2"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:pathData="m105.94,95.45a19.45,19.48 0,0 1,-19.45 19.48,19.45 19.48,0 0,1 -19.45,-19.48 19.45,19.48 0,0 1,19.45 -19.48,19.45 19.48,0 0,1 19.45,19.48"
android:strokeLineJoin="round"
android:strokeWidth="4"
android:fillColor="#dbdbdb"
android:strokeColor="#a6a6a6"
android:strokeLineCap="round"/>
<path
android:pathData="m84.05,81.68v2.36c-1.24,0.28 -2.31,0.8 -3.2,1.56 -1.6,1.34 -2.39,3.09 -2.39,5.25 0,1.53 0.35,2.76 1.06,3.68 0.71,0.92 1.61,1.61 2.7,2.05 1.1,0.44 2.47,0.83 4.13,1.18 1.28,0.29 2.26,0.58 2.93,0.88 0.68,0.3 1.01,0.85 1.01,1.66 0,0.85 -0.35,1.46 -1.06,1.84 -0.71,0.37 -1.53,0.56 -2.46,0.56 -2.52,0 -4.08,-1.06 -4.67,-3.17l-4.82,1.14c0.47,2.07 1.53,3.62 3.18,4.65 1.07,0.67 2.28,1.11 3.61,1.34v2.61h4.86v-2.64c1.57,-0.27 2.88,-0.81 3.91,-1.61 1.83,-1.44 2.75,-3.31 2.75,-5.61 0,-2.26 -0.97,-4.02 -2.9,-5.3 -0.69,-0.47 -1.42,-0.82 -2.18,-1.03 -0.75,-0.21 -1.98,-0.51 -3.69,-0.89 -2.18,-0.36 -3.27,-1.11 -3.27,-2.24 0,-1.42 1.04,-2.13 3.12,-2.13 1.96,0 3.25,0.88 3.89,2.65l4.41,-1.45c-0.98,-2.81 -2.99,-4.49 -6.04,-5.05v-2.31z"
android:strokeWidth="1.233"
android:fillColor="#a6a6a6"/>
</vector>
16 changes: 16 additions & 0 deletions res/graphics/quest/ferry_fee.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.