Skip to content

Commit

Permalink
Detect incorrect bridge/tunnel-highway connection
Browse files Browse the repository at this point in the history
Implements a check to detect (non-bridge/tunnel) highways that are connected to the (non-start/end) nodes of tunnels or bridges.

For tunnels I only enabled it for 'higher priority' ways due to many false positives with e.g. footways etc.

For bridges I disabled boardwalks as hiking routes apparently sometimes require people to step off a boardwalk (which is typically not too high).

In all cases I excluded steps and anything that may be indoor/building related
  • Loading branch information
Famlam committed Aug 8, 2024
1 parent e2dcccc commit 08a0e53
Show file tree
Hide file tree
Showing 2 changed files with 313 additions and 0 deletions.
98 changes: 98 additions & 0 deletions analysers/analyser_osmosis_highway_tunnel_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,57 @@
0 = SUM(CASE WHEN layer IS NULL THEN 0 ELSE 1 END) + CASE WHEN blayer IS NULL THEN 0 ELSE 1 END
"""

sql40 = """
SELECT DISTINCT
nodes.id,
bt_ways.id,
bt_connections.id,
ST_AsText(nodes.geom),
CASE
WHEN bt_ways.tags?'bridge' AND bt_ways.tags->'bridge'!='no' THEN 'bridge'
ELSE 'tunnel'
END
FROM
{0}highways AS bt_ways
JOIN {1}highways AS bt_connections ON
bt_connections.linestring && bt_ways.linestring AND
bt_connections.nodes && bt_ways.nodes AND
bt_connections.id != bt_ways.id
JOIN nodes ON
nodes.geom && bt_connections.linestring AND nodes.geom && bt_ways.linestring AND -- One is redundant, but let the planner choose
nodes.id = ANY(bt_ways.nodes) AND
nodes.id = ANY(bt_connections.nodes) AND
nodes.id != bt_ways.nodes[1] AND
nodes.id != bt_ways.nodes[array_length(bt_ways.nodes,1)]
WHERE
(
(
bt_ways.tags?'bridge' AND bt_ways.tags->'bridge' NOT IN ('no', 'boardwalk') AND
(NOT bt_connections.tags?'bridge' OR bt_connections.tags->'bridge' = 'no') AND
(NOT bt_connections.tags?'man_made' OR bt_connections.tags->'man_made' != 'pier')
) OR (
-- Tunnels for 'low level' highways give many false positives, hence only enable for crossing 'car roads'
bt_ways.level <= 4 AND bt_connections.level <= 4 AND
bt_ways.tags?'tunnel' AND bt_ways.tags->'tunnel' NOT IN ('no', 'avalanche_protector') AND
(NOT bt_connections.tags?'tunnel' OR bt_connections.tags->'tunnel' = 'no') AND
(NOT bt_connections.tags?'covered' OR bt_connections.tags->'covered' = 'no')
)
) AND
bt_ways.highway NOT IN ('steps') AND
bt_connections.highway NOT IN ('steps') AND
NOT bt_ways.is_construction AND NOT bt_connections.is_construction AND
-- Below: filter all cases where one would for instance walk from a building directly onto a bridge
(NOT bt_connections.tags?'indoor' OR bt_connections.tags->'indoor' = 'no') AND
NOT bt_connections.tags?'location' AND
NOT bt_connections.tags?'level'
"""


class Analyser_Osmosis_Highway_Tunnel_Bridge(Analyser_Osmosis):

requires_tables_full = ['highways']
requires_tables_diff = ['highways', 'touched_highways', 'not_touched_highways']

def __init__(self, config, logger = None):
Analyser_Osmosis.__init__(self, config, logger)
self.classs_change[1] = self.def_class(item = 7012, level = 3, tags = ['tag', 'highway', 'fix:survey'],
Expand All @@ -117,16 +166,40 @@ def __init__(self, config, logger = None):
# title = T_('Missing maxheight tag'))
#self.classs_change[3] = self.def_class(item = 7130, level = 3, tags = ['tag', 'highway', 'layer', "fix:imagery"],
# title = T_('Missing layer tag around bridge'))
self.classs_change[4] = self.def_class(item = 7012, level = 3, tags = ['highway', 'fix:survey', 'fix:imagery'],
title = T_('Bridge connected to non-bridge highway'),
detail = T_(
'''A bridge or tunnel is usually not connected to regular highways except at the end points.'''),
fix = T_(
'''Disconnect the bridge or tunnel from the highway, or add missing bridge or tunnel tags.
If the highway is truely connected to the bridge or tunnel, it may only be by a short section of this highway.
If so, you may have to split the connecting way, and adding bridge or tunnel tags only on the relevant part.'''),
trap = T_(
'''There might be bad detections with connections at the bridge heads or tunnel entrances.'''))
self.classs_change[5] = self.def_class(item = 7012, level = 3, tags = ['highway', 'fix:survey', 'fix:imagery'],
title = T_('Tunnel connected to non-tunnel highway'),
detail = T_(
'''A bridge or tunnel is usually not connected to regular highways except at the end points.'''),
fix = T_(
'''Disconnect the bridge or tunnel from the highway, or add missing bridge or tunnel tags.
If the highway is truely connected to the bridge or tunnel, it may only be by a short section of this highway.
If so, you may have to split the connecting way, and adding bridge or tunnel tags only on the relevant part.'''),
trap = T_(
'''There might be bad detections with connections at the bridge heads or tunnel entrances.'''))

self.callback10 = lambda res: {"class":1, "data":[self.way_full, self.positionAsText], "fix":[{"+":{"bridge:structure":"beam"}}, {"+":{"bridge:structure":"suspension"}}] }
#self.callback20 = lambda res: {"class":2, "data":[self.way_full, self.way_full, self.positionAsText] }
#self.callback30 = lambda res: {"class":3, "data":[self.way_full, self.positionAsText] }
self.callback40 = lambda res: {"class": 4 if res[4] == 'bridge' else 5, "data": [self.node_full, self.way, self.way_full, self.positionAsText] }

def analyser_osmosis_full(self):
self.run(sql10.format(""), self.callback10)
#self.run(sql20.format("", ""))
#self.run(sql21, self.callback20)
#self.run(sql30.format("", ""), self.callback30)
self.run(sql40.format("", ""), self.callback40)

def analyser_osmosis_diff(self):
self.run(sql10.format("touched_"), self.callback10)
Expand All @@ -136,3 +209,28 @@ def analyser_osmosis_diff(self):
#self.run(sql20.format("", "touched_"))
#self.run(sql21, self.callback20)
#self.run(sql30, self.callback30)
self.run(sql40.format("touched_", ""), self.callback40)
self.run(sql40.format("not_touched_", "touched_"), self.callback40)

from .Analyser_Osmosis import TestAnalyserOsmosis

class Test(TestAnalyserOsmosis):
@classmethod
def setup_class(cls):
from modules import config
TestAnalyserOsmosis.setup_class()
cls.analyser_conf = cls.load_osm("tests/osmosis_highway_tunnel_bridge.osm",
config.dir_tmp + "/tests/osmosis_highway_tunnel_bridge.test.xml",
{"proj": 23032})

def test_classes(self):
with Analyser_Osmosis_Highway_Tunnel_Bridge(self.analyser_conf, self.logger) as a:
a.analyser()

self.root_err = self.load_errors()
self.check_err(cl="4", elems=[("node", "26"), ("way", "1008"), ("way", "1013")])
self.check_err(cl="4", elems=[("node", "30"), ("way", "1008"), ("way", "1014")])
self.check_err(cl="4", elems=[("node", "39"), ("way", "1008"), ("way", "1019")])
self.check_err(cl="4", elems=[("node", "39"), ("way", "1008"), ("way", "1020")])
self.check_err(cl="5", elems=[("node", "13"), ("way", "1004"), ("way", "1005")])
self.check_num_err(5)
215 changes: 215 additions & 0 deletions tests/osmosis_highway_tunnel_bridge.osm
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
<?xml version='1.0' encoding='UTF-8'?>
<osm version='0.6' generator='JOSM'>
<node id='1' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86655703664' lon='5.88353279395' />
<node id='2' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86625541463' lon='5.88354121565' />
<node id='3' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86643582039' lon='5.88353617848'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='4' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86643916161' lon='5.8837517583' />
<node id='5' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86666971101' lon='5.88367034847' />
<node id='6' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.8665084999' lon='5.88367596294' />
<node id='7' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86660036776' lon='5.88367276348'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='8' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86659863952' lon='5.88354963736' />
<node id='9' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86634902586' lon='5.88407757914'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='10' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86653796864' lon='5.88406616865' />
<node id='11' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642009356' lon='5.88393422859' />
<node id='12' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642876086' lon='5.88424863894' />
<node id='13' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642391683' lon='5.88407305638'>
<tag k='note' v='Bad node class 4/5' />
</node>
<node id='14' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86628228247' lon='5.88408160984' />
<node id='15' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86649376466' lon='5.88398476023' />
<node id='16' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.8664980983' lon='5.88422758608' />
<node id='17' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86649526357' lon='5.88406874766'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='18' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86611327022' lon='5.88532661728'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='19' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86666797756' lon='5.88531819557'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='20' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86600752836' lon='5.88532661728'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='21' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86677718476' lon='5.88521432787' />
<node id='22' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86660901291' lon='5.88531909079'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='23' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86664717616' lon='5.88547259351' />
<node id='24' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86657266235' lon='5.88531964268'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='25' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86657090427' lon='5.88519467722' />
<node id='26' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86651016597' lon='5.88532059152'>
<tag k='note' v='Bad node class 4/5' />
</node>
<node id='27' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86651023336' lon='5.88547259351' />
<node id='28' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86645996311' lon='5.88521152063' />
<node id='29' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86645822965' lon='5.88544732839' />
<node id='30' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86645915562' lon='5.88532136597'>
<tag k='note' v='Bad node class 4/5' />
</node>
<node id='31' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86659170484' lon='5.88580805953' />
<node id='32' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86649029774' lon='5.88581086676' />
<node id='33' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86653255254' lon='5.88580969703'>
<tag k='note' v='No match class 4/5' />
</node>
<node id='34' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86653255008' lon='5.88584315102' />
<node id='35' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86590387912' lon='5.88538653621' />
<node id='36' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86590777946' lon='5.88527733005' />
<node id='37' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642573688' lon='5.88526000595' />
<node id='38' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642358994' lon='5.88538517202' />
<node id='39' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86642467541' lon='5.88532188946'>
<tag k='note' v='Bad node class 4/5' />
</node>
<node id='40' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86638278174' lon='5.88519167089' />
<node id='41' timestamp='2014-03-31T22:00:00Z' version='1' lat='51.86638063642' lon='5.88544876326' />
<way id='1000' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='1' />
<nd ref='3' />
<nd ref='2' />
<tag k='highway' v='pedestrian' />
<tag k='tunnel' v='yes' />
</way>
<way id='1001' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='3' />
<nd ref='4' />
<tag k='highway' v='footway' />
<tag k='incline' v='up' />
<tag k='note' v='For example a tunnel under railway platforms where a footway emerges that goes up to the platform. Should technically have cutting, but this is so often omitted' />
</way>
<way id='1002' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='5' />
<nd ref='7' />
<nd ref='6' />
<tag k='highway' v='footway' />
<tag k='tunnel' v='building_passage' />
</way>
<way id='1003' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='7' />
<nd ref='8' />
<tag k='covered' v='yes' />
<tag k='highway' v='footway' />
<tag k='indoor' v='yes' />
</way>
<way id='1004' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='9' />
<nd ref='13' />
<nd ref='17' />
<nd ref='10' />
<tag k='highway' v='tertiary' />
<tag k='tunnel' v='yes' />
</way>
<way id='1005' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='11' />
<nd ref='13' />
<nd ref='12' />
<tag k='highway' v='secondary' />
</way>
<way id='1006' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='14' />
<nd ref='9' />
<tag k='highway' v='tertiary' />
</way>
<way id='1007' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='15' />
<nd ref='17' />
<nd ref='16' />
<tag k='highway' v='secondary' />
<tag k='tunnel' v='yes' />
</way>
<way id='1008' action='modify' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='18' />
<nd ref='39' />
<nd ref='30' />
<nd ref='26' />
<nd ref='24' />
<nd ref='22' />
<nd ref='19' />
<tag k='bridge' v='cantilever' />
<tag k='highway' v='secondary' />
</way>
<way id='1009' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='20' />
<nd ref='18' />
<tag k='highway' v='secondary' />
</way>
<way id='1010' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='21' />
<nd ref='19' />
<tag k='bridge' v='yes' />
<tag k='highway' v='secondary' />
</way>
<way id='1011' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='22' />
<nd ref='23' />
<tag k='bridge' v='yes' />
<tag k='highway' v='tertiary' />
</way>
<way id='1012' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='24' />
<nd ref='25' />
<tag k='highway' v='steps' />
</way>
<way id='1013' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='26' />
<nd ref='27' />
<tag k='highway' v='tertiary' />
</way>
<way id='1014' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='28' />
<nd ref='30' />
<nd ref='29' />
<tag k='highway' v='tertiary' />
</way>
<way id='1015' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='31' />
<nd ref='33' />
<nd ref='32' />
<tag k='bridge' v='boardwalk' />
<tag k='highway' v='path' />
</way>
<way id='1016' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='33' />
<nd ref='34' />
<tag k='highway' v='path' />
<tag k='note' v='Hiking path that may get you wet shoes starting with a jump from the boardwalk' />
</way>
<way id='1017' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='35' />
<nd ref='20' />
<tag k='bridge' v='yes' />
<tag k='highway' v='secondary' />
<tag k='note' v='Class 1 no match' />
<tag k='oneway' v='yes' />
</way>
<way id='1018' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='20' />
<nd ref='36' />
<tag k='bridge' v='yes' />
<tag k='highway' v='secondary' />
<tag k='note' v='Class 1 match' />
<tag k='oneway' v='yes' />
</way>
<way id='1019' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='37' />
<nd ref='39' />
<tag k='highway' v='residential' />
</way>
<way id='1020' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='39' />
<nd ref='38' />
<tag k='highway' v='residential' />
</way>
<way id='1021' timestamp='2014-03-31T22:00:00Z' version='1'>
<nd ref='40' />
<nd ref='41' />
<tag k='highway' v='primary' />
<tag k='layer' v='-1' />
</way>
</osm>

0 comments on commit 08a0e53

Please sign in to comment.