-
-
Notifications
You must be signed in to change notification settings - Fork 339
/
AddMaxHeight.kt
173 lines (147 loc) · 7.18 KB
/
AddMaxHeight.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package de.westnordost.streetcomplete.quests.max_height
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.geometry.ElementPolylinesGeometry
import de.westnordost.streetcomplete.data.osm.mapdata.Element
import de.westnordost.streetcomplete.data.osm.mapdata.MapDataWithGeometry
import de.westnordost.streetcomplete.data.osm.osmquests.OsmElementQuestType
import de.westnordost.streetcomplete.data.user.achievements.EditTypeAchievement.CAR
import de.westnordost.streetcomplete.osm.ALL_PATHS
import de.westnordost.streetcomplete.osm.ALL_ROADS
import de.westnordost.streetcomplete.osm.Tags
import de.westnordost.streetcomplete.util.ktx.containsAny
import de.westnordost.streetcomplete.util.math.intersects
class AddMaxHeight : OsmElementQuestType<MaxHeightAnswer> {
private val nodeFilter by lazy { """
nodes with
(
barrier = height_restrictor
or amenity = parking_entrance and parking ~ underground|multi-storey
)
and $noMaxHeight
""".toElementFilterExpression() }
private val roadsWithoutMaxHeightFilter by lazy { """
ways with
(
highway ~ motorway|motorway_link|trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified|residential|living_street|track|road
or (highway = service and access !~ private|no and vehicle !~ private|no)
)
and $noMaxHeight
""".toElementFilterExpression() }
private val railwayCrossingsFilter by lazy { """
nodes with
railway = level_crossing
and $noMaxHeight
""".toElementFilterExpression() }
private val electrifiedRailwaysFilter by lazy { """
ways with
railway and railway != tram
and electrified = contact_line
""".toElementFilterExpression() }
// not trams because people tell me it is extremely unlikely that it is signed - at least
// directly at the crossing, anyway. Also, since a tram crosses with a road so often, it is
// kind of spammy, especially if the answer is virtually always(?) "not signed"
private val allRoadsFilter by lazy { """
ways with highway ~ ${ALL_ROADS.joinToString("|")}
""".toElementFilterExpression() }
private val tunnelFilter by lazy { """
ways with highway and (covered = yes or tunnel ~ yes|building_passage|avalanche_protector)
""".toElementFilterExpression() }
private val bridgeFilter by lazy { """
ways with (
highway ~ ${(ALL_ROADS + ALL_PATHS).joinToString("|")}
or railway ~ rail|light_rail|subway|narrow_gauge|tram|disused|preserved|funicular|monorail
) and (
bridge and bridge != no
or man_made = pipeline and location = overhead
)
and layer
""".toElementFilterExpression() }
private val noMaxHeight = """
!maxheight
and !maxheight:signed
and !maxheight:physical
and (!maxheight:forward or !maxheight:backward)
"""
override val changesetComment = "Specify maximum heights"
override val wikiLink = "Key:maxheight"
override val icon = R.drawable.ic_quest_max_height
override val achievements = listOf(CAR)
override fun getTitle(tags: Map<String, String>): Int {
val isBelowBridge = tags["amenity"] != "parking_entrance"
&& tags["barrier"] != "height_restrictor"
&& tags["tunnel"] == null
&& tags["covered"] == null
&& tags["man_made"] != "pipeline"
&& tags["railway"] != "level_crossing"
// only the "below the bridge" situation may need some context
return when {
isBelowBridge -> R.string.quest_maxheight_sign_below_bridge_title
else -> R.string.quest_maxheight_sign_title
}
}
override fun getApplicableElements(mapData: MapDataWithGeometry): Iterable<Element> {
// amenity = parking_entrance nodes etc. only if they are a vertex in a road
val roadsNodeIds = mapData.ways
.filter { allRoadsFilter.matches(it) }
.flatMapTo(HashSet()) { it.nodeIds }
val nodesWithoutHeight = mapData.nodes
.filter { it.id in roadsNodeIds && nodeFilter.matches(it) }
// railway crossings with railways that have an electrified contact line
val electrifiedRailwayNodeIds = mapData.ways
.filter { electrifiedRailwaysFilter.matches(it) }
.flatMapTo(HashSet()) { it.nodeIds }
val railwayCrossingNodesWithoutHeight = mapData.nodes
.filter { it.id in electrifiedRailwayNodeIds && railwayCrossingsFilter.matches(it) }
// tunnels without height
val roadsWithoutHeight = mapData.ways.filter { roadsWithoutMaxHeightFilter.matches(it) }
val tunnelsWithoutHeight = roadsWithoutHeight.filter { tunnelFilter.matches(it) }
// ways below bridges without height
val bridges = mapData.ways.filter { bridgeFilter.matches(it) }
val waysBelowBridgesWithoutHeight = roadsWithoutHeight.filter { way ->
val layer = way.tags["layer"]?.toIntOrNull() ?: 0
val geometry = mapData.getWayGeometry(way.id) as? ElementPolylinesGeometry
// applicable if with any bridge...
geometry != null && bridges.any { bridge ->
val bridgeGeometry = mapData.getWayGeometry(bridge.id) as? ElementPolylinesGeometry
val bridgeLayer = bridge.tags["layer"]?.toIntOrNull() ?: 0
// , that is in a layer above this way
bridgeLayer > layer
// and with which it does not share any node (=connects) (#2555)
&& !bridge.nodeIds.toSet().containsAny(way.nodeIds)
// , it intersects
&& bridgeGeometry != null && bridgeGeometry.intersects(geometry)
}
}
return nodesWithoutHeight +
railwayCrossingNodesWithoutHeight +
tunnelsWithoutHeight +
waysBelowBridgesWithoutHeight
}
override fun isApplicableTo(element: Element): Boolean? {
if (roadsWithoutMaxHeightFilter.matches(element)) {
if (tunnelFilter.matches(element)) return true
// if it is a way but not a tunnel, we cannot determine whether it is applicable (=
// below a bridge) just by looking at the tags
return null
}
// for nodes that may be applicable we cannot finally determine it because that node must be
// a vertex of a road
if (nodeFilter.matches(element)) return null
// railway crossing
if (railwayCrossingsFilter.matches(element)) return null
return false
}
override fun createForm() = AddMaxHeightForm()
override fun applyAnswerTo(answer: MaxHeightAnswer, tags: Tags, geometry: ElementGeometry, timestampEdited: Long) {
when (answer) {
is MaxHeight -> {
tags["maxheight"] = answer.value.toOsmValue()
}
is NoMaxHeightSign -> {
tags["maxheight:signed"] = "no"
}
}
}
}