Skip to content

Instantly share code, notes, and snippets.

@jwheare
Created December 13, 2025 13:39
Show Gist options
  • Select an option

  • Save jwheare/a3577de092c68b1789e454e832b38f5b to your computer and use it in GitHub Desktop.

Select an option

Save jwheare/a3577de092c68b1789e454e832b38f5b to your computer and use it in GitHub Desktop.
OSM slipway analysis - takes overpass geojson as input
#!/usr/bin/env python3
import json
import sys
from colors import color # pip install ansicolors
def main(input_path):
with open(input_path, "r") as f:
geojson = json.load(f)
features = geojson['features']
f_dict = {
'node': {},
'way': {},
}
for f in features:
props = f['properties']
f_type = props['type']
f_id = props['id']
if f_type in f_dict:
f_dict[f_type][f_id] = f
else:
print(f'[{f_id}] unknown type: {f_type}')
orphan_nodes = []
for n_id, n_feat in f_dict['node'].items():
n_ways = []
for w_id, w_feat in f_dict['way'].items():
if n_id in w_feat['properties']['nodes']:
n_ways.append(w_id)
if not len(n_ways):
orphan_nodes.append(n_id)
print(f'[{n_id}] orphan node')
n_feat['properties']['ways'] = n_ways
missing_slip_node = []
slip_node_starts = []
slip_node_middles = []
slip_node_ends = []
way_tags = [
('leisure', 'slipway', "magenta"),
('service', 'slipway', "purple"),
('highway', 'service', "blue"),
]
way_tag_counts = {}
way_full_tag_counts = {}
for w_id, w_feat in f_dict['way'].items():
has_slip_node = False
slip_node_at_end = False
slip_node_at_start = False
slip_node_in_middle = False
w_nodes = w_feat['properties']['nodes']
for n_id in w_nodes:
if n_id in f_dict['node']:
has_slip_node = True
slip_node_index = w_nodes.index(n_id)
if slip_node_index == 0:
slip_node_at_start = True
elif slip_node_index == len(w_nodes) - 1:
slip_node_at_end = True
else:
slip_node_in_middle = True
tag_parts = []
w_tags = w_feat['properties']['tags']
key_parts = []
for t, v, COLOR in way_tags:
if t in w_tags:
k = f'{t}={w_tags[t]}'
key_parts.append(k)
if k not in way_tag_counts:
way_tag_counts[k] = 0
way_tag_counts[k] += 1
V_COL = "gray"
if w_tags[t] != v:
V_COL = "orange"
tag_parts.append(f' {color(t, COLOR)} = {color(w_tags[t], V_COL)}')
slip_node_status = color('none', 'grey')
if has_slip_node:
pos_parts = []
if slip_node_at_end:
slip_node_ends.append(w_id)
pos_parts.append(color('end', 'green'))
if slip_node_at_start:
print(f'[{w_id}] way has slip node at start ({len(w_feat['properties']['nodes'])})')
pos_parts.append(color('start', 'yellow'))
slip_node_starts.append(w_id)
if slip_node_in_middle:
print(f'[{w_id}] way has slip node in middle ({len(w_feat['properties']['nodes'])})')
slip_node_middles.append(w_id)
pos_parts.append(color('middle', 'red'))
slip_node_status = ",".join(pos_parts)
else:
missing_slip_node.append(w_id)
print(f'[{w_id}] way missing leisure=slip node ({len(w_feat['properties']['nodes'])})')
full_key = (';'.join(key_parts), slip_node_status)
if full_key not in way_full_tag_counts:
way_full_tag_counts[full_key] = 0
way_full_tag_counts[full_key] += 1
if not has_slip_node or slip_node_at_start or slip_node_in_middle:
for p in tag_parts:
print(p)
print(f'nodes: {len(f_dict['node'])}')
print(f' orphans: {len(orphan_nodes)}')
print(f'ways: {len(f_dict['way'])}')
print(f' leisure=slip node missing: {len(missing_slip_node)}')
print(f' leisure=slip node at start: {len(slip_node_starts)}')
print(f' leisure=slip node in middle: {len(slip_node_middles)}')
print(f' leisure=slip node at end: {len(slip_node_ends)}')
print('way tags')
for k, t_count in sorted(way_tag_counts.items(), key=lambda x: x[1], reverse=True):
print(f' {k} ({t_count})')
print('way tags (full)')
for (k, slip_node), t_count in sorted(way_full_tag_counts.items(), key=lambda x: (x[0][1], x[1]), reverse=True):
print(f' slip_node={slip_node} {k} ({t_count})')
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python slipways.py input.json")
sys.exit(1)
input_path = sys.argv[1]
main(input_path)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment