"""Writes client/ios/App/TREKWidgets/WorldGrid.swift: the world as a grid of dots, each with the ISO code of the country under it, for the atlas widget. Run from the repo root after the country shapes change: python client/ios/WidgetPreviews/make-world-grid.py Reads server/assets/atlas/admin0.geojson.gz, the same shapes the Atlas page draws. """ import gzip import json COLS, ROWS = 76, 34 LAT_TOP, LAT_BOTTOM = 80.0, -58.0 # Antarctica and the far north stay off the map SOURCE = 'server/assets/atlas/admin0.geojson.gz' TARGET = 'client/ios/App/TREKWidgets/WorldGrid.swift' def rings(geometry): if geometry['type'] == 'Polygon': yield geometry['coordinates'] elif geometry['type'] == 'MultiPolygon': yield from geometry['coordinates'] def inside(ring, x, y): hit = False j = len(ring) - 1 for i in range(len(ring)): xi, yi = ring[i][0], ring[i][1] xj, yj = ring[j][0], ring[j][1] if (yi > y) != (yj > y) and x < (xj - xi) * (y - yi) / ((yj - yi) or 1e-12) + xi: hit = not hit j = i return hit def in_polygon(polygon, x, y): return inside(polygon[0], x, y) and not any(inside(hole, x, y) for hole in polygon[1:]) features = [] for feature in json.load(gzip.open(SOURCE))['features']: code = feature['properties'].get('ISO_A2') or '' if len(code) != 2 or not code.isalpha(): continue for polygon in rings(feature['geometry']): xs = [p[0] for p in polygon[0]] ys = [p[1] for p in polygon[0]] features.append((code, (min(xs), min(ys), max(xs), max(ys)), polygon)) # Each cell is sampled at five points, so a narrow country still claims its dot. offsets = [(0.5, 0.5), (0.25, 0.25), (0.75, 0.25), (0.25, 0.75), (0.75, 0.75)] cell_w = 360.0 / COLS cell_h = (LAT_TOP - LAT_BOTTOM) / ROWS rows = [] for r in range(ROWS): row = [] for c in range(COLS): votes = {} for ox, oy in offsets: x = -180 + (c + ox) * cell_w y = LAT_TOP - (r + oy) * cell_h for code, (x0, y0, x1, y1), polygon in features: if x0 <= x <= x1 and y0 <= y <= y1 and in_polygon(polygon, x, y): votes[code] = votes.get(code, 0) + 1 break row.append(max(votes, key=votes.get) if votes else '..') rows.append(''.join(row)) land = sum(len(r) // 2 - r.count('..') for r in rows) body = '\n'.join(f' "{r}",' for r in rows) swift = f'''// Generated by client/ios/WidgetPreviews/make-world-grid.py from // server/assets/atlas/admin0.geojson.gz; do not edit by hand. import Foundation /// The world as {COLS} by {ROWS} dots from {LAT_TOP:g}° north to {-LAT_BOTTOM:g}° south, /// each the ISO code of the country under it, ".." for sea ({land} land dots). enum WorldGrid {{ static let columns = {COLS} static let rows: [String] = [ {body} ] /// The country code of a dot, nil for sea. static func code(row: Int, column: Int) -> String? {{ let line = rows[row] let start = line.index(line.startIndex, offsetBy: column * 2) let code = String(line[start..