#!/usr/bin/env python3 """Offline, standard-library-only conversion of Fla-shop SVG to identity spans. SVG input uses straight M/L/H/V/Z paths. Flood regions at original resolution, associate via documented seed coordinates, then reduce to guest scanline runs. """ import collections, json, math, re, struct, zlib from pathlib import Path import xml.etree.ElementTree as ET ROOT=Path(__file__).resolve().parents[1] W,H=2000,1200 rows=[s.split('\t') for s in (ROOT/'data/states.tsv').read_text().splitlines()] assert len(rows)==50 and len({r[0] for r in rows})==50 ids={r[0]:i+1 for i,r in enumerate(rows)} def paths(d): tok=re.findall(r'[MmLlHhVvZz]|[-+]?(?:\d*\.\d+|\d+)(?:[eE][-+]?\d+)?',d) i=0;x=y=0;out=[];poly=[];cmd='' while iI',len(b))+tag+b+struct.pack('>I',zlib.crc32(tag+b)&0xffffffff) raw=b''.join(b'\0'+bytes(pixels[y*w*3:(y+1)*w*3]) for y in range(h)) path.write_bytes(b'\x89PNG\r\n\x1a\n'+chunk(b'IHDR',struct.pack('>IIBBBBB',w,h,8,2,0,0,0))+chunk(b'IDAT',zlib.compress(raw))+chunk(b'IEND',b'')) def main(): svg=ET.parse(ROOT/'assets/usa-source.svg').getroot() elements={e.get('id'):e for e in svg.iter() if e.get('id')} land=bytearray(W*H) outline=paths(elements['outline'].get('d')) # Even-odd scan conversion, coast and islands included. edges=[(a,b) for p in outline for a,b in zip(p,p[1:]+p[:1]) if a[1]!=b[1]] buckets=[[] for _ in range(H)] for a,b in edges: for y in range(max(0,math.ceil(min(a[1],b[1])-.5)),min(H,math.ceil(max(a[1],b[1])-.5))): buckets[y].append(a[0]+(y+.5-a[1])*(b[0]-a[0])/(b[1]-a[1])) for y,xx in enumerate(buckets): xx.sort() for a,b in zip(xx[::2],xx[1::2]): lo=max(0,math.ceil(a-.5));hi=min(W,math.ceil(b-.5));land[y*W+lo:y*W+hi]=b'\1'*(hi-lo) coast=bytearray(land) # Three-source-pixel boundary stroke closes subpixel joins before flood fill. for p in paths(elements['boundaries'].get('d')): for a,b in zip(p,p[1:]): n=max(1,math.ceil(max(abs(a[0]-b[0]),abs(a[1]-b[1])))) for t in range(n+1): x=round(a[0]+(b[0]-a[0])*t/n);y=round(a[1]+(b[1]-a[1])*t/n) for yy in range(max(0,y-1),min(H,y+2)): for xx in range(max(0,x-1),min(W,x+2)):land[yy*W+xx]=0 seeds={} for e in svg.iter(): if e.tag.endswith('tspan'): ab=''.join(e.itertext()).strip() if ab in ids:seeds[ab]=[round(float(e.get('x'))+15),round(float(e.get('y'))-12)] # Callout labels are outside their states. Seed actual interiors instead. seeds.update({'VT':[1800,280],'NH':[1840,280],'MA':[1830,340], 'RI':[1860,360],'CT':[1830,375],'NJ':[1780,440], 'DE':[1764,509],'MD':[1720,536],'HI':[739,1130]}) seeds.update(json.loads((ROOT/'data/map-seeds.json').read_text()) if (ROOT/'data/map-seeds.json').exists() else {}) owners=bytearray(W*H);claimed={};counts={} for ab,(x,y) in seeds.items(): start=y*W+x assert land[start],(ab,'seed is not land',x,y) assert not owners[start],(ab,'shares component with',claimed.get(owners[start])) val=ids[ab];q=collections.deque([start]);owners[start]=val;count=0 while q: pos=q.popleft();count+=1 for nxt in (pos-1,pos+1,pos-W,pos+W): if 0<=nxt850:owners[p]=ids['AK'] elif 11758 and any(v and v!=val for v in (original[pos+1],original[pos+w])): small[pos]=0 spans=[];offsets=[0];total={} for val in range(1,51): cnt=0 for y in range(h): x=0 while x