Last active
January 11, 2026 10:53
-
-
Save jwheare/a3577de092c68b1789e454e832b38f5b to your computer and use it in GitHub Desktop.
OSM slipway analysis - takes overpass geojson as input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env python3 | |
| import json | |
| import sys | |
| from colors import color # pip install ansicolors | |
| DEBUG = False | |
| 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) | |
| if DEBUG: | |
| print(f'[{n_id}] orphan node') | |
| n_feat['properties']['ways'] = n_ways | |
| missing_slip_node = [] | |
| non_leisure_slip_tag = [] | |
| missing_leisure_slip_tag = [] | |
| slip_node_starts = [] | |
| slip_node_middles = [] | |
| slip_node_ends = [] | |
| leisure_slip_way = [] | |
| double_leisure_slip_tag = [] | |
| closed_ways = [] | |
| 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_leisure_slip_node = False | |
| has_non_leisure_slip_node = False | |
| slip_node_at_end = False | |
| slip_node_at_start = False | |
| slip_node_in_middle = False | |
| print_tags = False | |
| w_nodes = w_feat['properties']['nodes'] | |
| for n_id in w_nodes: | |
| if n_id in f_dict['node']: | |
| n_feat = f_dict['node'][n_id] | |
| n_tags = n_feat['properties']['tags'] | |
| n_leisure_tagged = n_tags.get('leisure') == 'slipway' | |
| if n_leisure_tagged: | |
| has_leisure_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 | |
| else: | |
| has_non_leisure_slip_node = True | |
| tag_parts = [] | |
| w_tags = w_feat['properties']['tags'] | |
| w_leisure_tagged = w_tags.get('leisure') == 'slipway' | |
| if w_leisure_tagged: | |
| leisure_slip_way.append(w_id) | |
| 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_leisure_slip_node: | |
| if w_leisure_tagged: | |
| double_leisure_slip_tag.append(w_id) | |
| if DEBUG: | |
| print_tags = True | |
| print(f'[{w_id}] way has slip node and tagged leisure=slipway ({len(w_feat['properties']['nodes'])})') | |
| pos_parts = [] | |
| if slip_node_at_end: | |
| slip_node_ends.append(w_id) | |
| pos_parts.append(color('end', 'green')) | |
| if slip_node_at_start: | |
| if DEBUG: | |
| print_tags = True | |
| 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: | |
| if DEBUG: | |
| print_tags = True | |
| 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: | |
| if has_non_leisure_slip_node: | |
| non_leisure_slip_tag.append(w_id) | |
| if DEBUG: | |
| print_tags = True | |
| print(f'[{w_id}] node has non leisure=slip tag ({len(w_feat['properties']['nodes'])})') | |
| if not w_leisure_tagged: | |
| missing_leisure_slip_tag.append(w_id) | |
| print_tags = True | |
| print(f'[{w_id}] way and node missing leisure=slip node ({len(w_feat['properties']['nodes'])})') | |
| missing_slip_node.append(w_id) | |
| if DEBUG: | |
| print(f'[{w_id}] way missing leisure=slip node ({len(w_feat['properties']['nodes'])})') | |
| if w_nodes[0] == w_nodes[-1]: | |
| closed_ways.append(w_id) | |
| print_tags = True | |
| print(f'[{w_id}] way is closed ({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 print_tags: | |
| 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 on way: {len(leisure_slip_way)}') | |
| print(f' leisure=slip not on node: {len(missing_slip_node)}') | |
| print(f' leisure=slip node at end: {len(slip_node_ends)}') | |
| print('issues:') | |
| print(f' closed ways: {len(closed_ways)}') | |
| print(f' non leisure=slip on node: {len(non_leisure_slip_tag)}') | |
| print(f' leisure=slip on neither: {len(missing_leisure_slip_tag)}') | |
| print(f' leisure=slip node and way: {len(double_leisure_slip_tag)}') | |
| print(f' leisure=slip node at start: {len(slip_node_starts)}') | |
| print(f' leisure=slip node in middle: {len(slip_node_middles)}') | |
| 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})') | |
| if DEBUG: | |
| 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