#!/usr/bin/env python3 """ Assemble world_ports.json from generate_ports.py + ports_part2.py + ports_part3.py Run: python assemble_ports.py """ import json import os import sys # Auto-assign region from coordinates (matches marinetraffic_parser.py logic) def assign_region(lat, lon): if lon < -100 and lat > 25: return 'USWC' elif lon < -30 and lat > 25: return 'USEC' elif lon < -30 and -5 < lat <= 25: return 'CARIB' elif lon < -70 and lat <= -5: return 'SAW' elif lon < -30 and lat <= -5: return 'SAE' elif lon > 100 and lat < -10: return 'AUSNZ' elif lon > 100: return 'EASIA' elif 60 < lon <= 100 and lat > 0: return 'SASIA' elif 40 < lon <= 60 and lat > 10: return 'GULF' elif 10 < lon < 45 and lat < -20: return 'SAFR' elif 30 < lon <= 45 and -10 < lat < 20: return 'ERED' elif -5 < lon < 15 and -5 < lat < 15: return 'WAFR' elif 25 < lon <= 40 and 40 < lat <= 48: return 'BSEA' elif lon < 40 and lat > 48: return 'NEUR' elif -10 <= lon <= 40 and 25 < lat <= 48: return 'MED' else: return 'OTHER' # Size code mapping SIZE_MAP = {'VL': 'large', 'L': 'large', 'M': 'medium', 'S': 'small', 'large': 'large', 'medium': 'medium', 'small': 'small'} def load_part1(): """Load ports from generate_ports.py (NEUR + BALTIC + SEUR)""" # Execute the script to get 'ports' list ns = {} script = os.path.join(os.path.dirname(__file__), 'generate_ports.py') with open(script, 'r', encoding='utf-8') as f: code = f.read() exec(code, ns) return ns.get('ports', []) def load_part2(): """Load ports from ports_part2.py (GULF + ERED + WAFR + SAFR)""" ns = {} script = os.path.join(os.path.dirname(__file__), 'ports_part2.py') if not os.path.exists(script): print(f"WARNING: {script} not found, skipping") return [] with open(script, 'r', encoding='utf-8') as f: code = f.read() exec(code, ns) return ns.get('part2_ports', []) def load_part3(): """Load ports from ports_part3.py (Americas + AUSNZ)""" ns = {} script = os.path.join(os.path.dirname(__file__), 'ports_part3.py') if not os.path.exists(script): print(f"WARNING: {script} not found, skipping") return [] with open(script, 'r', encoding='utf-8') as f: code = f.read() exec(code, ns) return ns.get('part3_ports', []) def main(): all_ports = [] print("Loading Part 1 (Europe)...") p1 = load_part1() print(f" -> {len(p1)} ports") all_ports.extend(p1) print("Loading Part 2 (Middle East + Africa)...") p2 = load_part2() print(f" -> {len(p2)} ports") all_ports.extend(p2) print("Loading Part 3 (Americas + Pacific)...") p3 = load_part3() print(f" -> {len(p3)} ports") all_ports.extend(p3) print(f"\nTotal raw ports: {len(all_ports)}") # Deduplicate by key, first occurrence wins seen = {} for port in all_ports: key = port.get('key', '') if not key: continue if key not in seen: seen[key] = port # Normalize and build final dict final = {} for key, port in seen.items(): # Normalize size port['size'] = SIZE_MAP.get(port.get('size', 'M'), 'medium') # Override region with coordinate-based for consistency port['region'] = assign_region(port['lat'], port['lon']) # Add radius_nm based on size if port['size'] == 'large': port['radius_nm'] = 12 elif port['size'] == 'medium': port['radius_nm'] = 8 else: port['radius_nm'] = 5 final[key] = port # Validate errors = 0 for key, port in final.items(): if not (-90 <= port['lat'] <= 90): print(f" ERROR: {key} lat={port['lat']}") errors += 1 if not (-180 <= port['lon'] <= 180): print(f" ERROR: {key} lon={port['lon']}") errors += 1 if errors: print(f"\n{errors} validation errors!") sys.exit(1) # Write JSON output = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'world_ports.json') with open(output, 'w', encoding='utf-8') as f: json.dump(final, f, indent=1, ensure_ascii=False) print(f"\nGenerated {len(final)} unique ports -> {output}") # Stats by region from collections import Counter regions = Counter(p['region'] for p in final.values()) for r, c in sorted(regions.items(), key=lambda x: -x[1]): print(f" {r}: {c}") # Stats by size sizes = Counter(p['size'] for p in final.values()) for s, c in sorted(sizes.items()): print(f" {s}: {c}") if __name__ == '__main__': main()