"""PELASGIA temporal atlas importer. Cliopatria CC BY 4.0; derived SVG geometries. Reproduce with Python, requests, shapely; pinned source commit and explicit corrections. """ import json,zipfile,hashlib,requests,copy,csv,gzip from pathlib import Path from shapely.geometry import shape,box,Polygon from shapely import make_valid R=Path(__file__).resolve().parent; A=R/'atlas-output';A.mkdir(exist_ok=True);SHA='5f433377139a6eeaeacbdc894b70f8805b72c8b5' URL=f'https://github.com/Seshat-Global-History-Databank/cliopatria/tree/{SHA}' ICTY='https://www.icty.org/en/about/what-former-yugoslavia';UN='https://treaties.un.org/pages/HistoricalInfo.aspx';GER='https://www.bundesregierung.de/breg-de/schwerpunkte/deutsche-einheit/die-einheit-ist-wirklichkeit-432814' s=requests.Session();s.headers['User-Agent']='PelasgiaAtlas/3.0' p=R/'cliopatria.geojson.zip' if not p.exists():rr=s.get(f'https://raw.githubusercontent.com/Seshat-Global-History-Databank/cliopatria/{SHA}/cliopatria.geojson.zip',timeout=120);rr.raise_for_status();p.write_bytes(rr.content) z=zipfile.ZipFile(p);raw=json.loads(z.read(z.namelist()[0]));source=raw['features'];rows=[] def find(name,y):return next(f for f in source if f['properties']['Name']==name and f['properties']['FromYear']<=y<=f['properties']['ToYear']) # Normalized intervals: FromYear/ToYear inclusive. Exact overrides: fromDate inclusive, untilDate exclusive. # Legal state outlines differ from wartime control. Do not import 1992-95 Serbia-Montenegro control expansion as sovereign territory. for i,f in enumerate(source): p=f['properties'];f=copy.deepcopy(f);f['_sourceRecord']=i;f['_entity']=p.get('Wikidata') or p['Name'];f['_note']='';f['_evidence']=[] if p.get('Type')!='POLITY':continue if p['ToYear']<-3000:continue if p['Name'] in ['Federal Republic of Germany','German Democratic Republic'] and p['FromYear']==1990:f['_untilDate']='1990-10-03';f['_note']='Der nur jahresgenaue Quellstand wurde anhand des Einigungsvertrags auf den 02.10.1990 begrenzt.';f['_evidence']=[GER] if p['Name']=='Federated Republic of Germany' and p['FromYear']==1991:p=f['properties'];p['FromYear']=1990;f['_fromDate']='1990-10-03';f['_note']='Historischer vereinigter Umriss aus Cliopatria (Quellintervall ab 1991); zeitlich korrigiert auf den belegten Beitritt am 03.10.1990. Generalisierte Rekonstruktion, keine vermessene Tagesgrenze.';f['_evidence']=[GER] if p['Name']=='Socialist Federal Republic of Yugoslavia' and p['FromYear']==1991: f['_untilDate']='1991-06-25';f['_note']='Für die Auflösung ab 25.06.1991 wird die pauschale Ganzjahresgeometrie nicht fortgeschrieben. Im Übergangsjahr verbleibt eine regionale Datenlücke.';f['_evidence']=[ICTY,UN] if p['Name'] in ['Serbia-Montenegro','Republic of Croatia','Bosnia and Herzegovina'] and p['FromYear']==1992: legal=find(p['Name'],1996);f['geometry']=copy.deepcopy(legal['geometry']);f['_geometryRecord']=source.index(legal);f['_fromDate']='1992-04-27' if p['Name']=='Serbia-Montenegro' else '1992-01-01';f['_note']='Rekonstruierter, generalisierter Staatsumriss aus dem Cliopatria-Stand 1996; nicht die wechselnden militärischen Kontrollflächen 1992–1995. Territoriale Abgrenzung anhand UN/ICTY eingeordnet; Grenzdetails bleiben unsicher.';f['_evidence']=[ICTY,UN,'https://peacekeeping.un.org/sites/default/files/past/unprof_p.htm'] if p['Name']=='Bosnia and Herzegovina':f['_fromDate']='1992-03-06' if p['Name']=='Serbia-Montenegro': if p['FromYear']<=2003: if p['FromYear']==2000:p=f['properties'];p['ToYear']=2003;f['_untilDate']='2003-02-04' f['_entity']='Q838261';f['_display']='Bundesrepublik Jugoslawien';f['_evidence']=list(set(f['_evidence']+[ICTY,UN])) if p['Name']=='Serbia-Montenegro' and p['FromYear']==2000: later=copy.deepcopy(f);later['properties']['FromYear']=2003;later['properties']['ToYear']=2005;later.pop('_untilDate',None);later['_fromDate']='2003-02-04';later['_entity']='Q37024';later['_display']='Serbien und Montenegro';rows.append(later) rows.append(f) # Date override for Slovenia and Macedonia follows the UN Treaty Collection's explicit convention. for name,start in [('Republic of Slovenia','1991-06-25'),('Former Yugoslav Republic of Macedonia','1991-11-17')]: f=copy.deepcopy(next(x for x in rows if x['properties']['Name']==name and x['properties']['FromYear']==1992));f['properties']['FromYear']=1991;f['_fromDate']=start;f['_note']='Historischer Jahresumriss; Beginn nach der UN Treaty Collection. Jahresgenaue Geometrie, keine tagesgenau vermessene Grenze.';f['_evidence']=[UN];rows=[x for x in rows if x is not next((a for a in rows if a['properties']['Name']==name and a['properties']['FromYear']==1992),None)];rows.append(f) # Croatia's 1991 wartime territorial outline is not fabricated from later geometry: show a named data gap. def projection(poly):return ''.join('M'+'L'.join(f'{(x+25)*9:.2f},{(75-y)*14:.2f}' for x,y,*_ in ring.coords)+'Z' for ring in [poly.exterior,*poly.interiors]) features=[] for f in rows: try:g=make_valid(shape(f['geometry'])).intersection(box(-25,10,180,78)).simplify(.025,preserve_topology=True) except Exception:continue polys=[g] if isinstance(g,Polygon) else [p for p in getattr(g,'geoms',[]) if isinstance(p,Polygon)] polys=[p for p in polys if p.area>.001] if not polys:continue p=f['properties'];bb=g.bounds;entity=f['_entity'];name=f.get('_display',p['Name']);name={'Socialist Federal Republic of Yugoslavia':'Sozialistische Föderative Republik Jugoslawien','Federal Republic of Germany':'Bundesrepublik Deutschland (West)','Federated Republic of Germany':'Bundesrepublik Deutschland','German Democratic Republic':'Deutsche Demokratische Republik'}.get(name,name) record={'id':'clio-'+hashlib.sha256(f"{f['_sourceRecord']}:{p['FromYear']}:{p['ToYear']}:{entity}".encode()).hexdigest()[:14],'entity':entity,'qid':entity if entity.startswith('Q') else None,'name':name,'group':p.get('MemberOf') or entity,'from':p['FromYear'],'to':p['ToYear'],'fromDate':f.get('_fromDate'),'untilDate':f.get('_untilDate'),'d':''.join(projection(p) for p in polys),'bbox':[round((bb[0]+25)*9,2),round((75-bb[3])*14,2),round((bb[2]+25)*9,2),round((75-bb[1])*14,2)],'sourceRecord':f['_sourceRecord'],'geometryRecord':f.get('_geometryRecord',f['_sourceRecord']),'precision':'reconstructed' if f['_note'] else 'year','note':f['_note'],'evidence':f['_evidence'],'memberOf':p.get('MemberOf','')} features.append(record) # Preserve a readable hierarchy: dependencies remain separate and are explicitly marked, never silently sovereign. # Audited name period and Soviet dissolution; keep the historical geometry and source identity. extra=[] for f in features: if f['entity']=='Q83286' and f['from']<1963<=f['to']: old=copy.deepcopy(f);old['id']+='-fpr';old['to']=1963;old['untilDate']='1963-04-07';old['name']='Föderative Volksrepublik Jugoslawien';old['note']='Historischer Name vor der Verfassung von 1963; gleicher historischer Quellumriss.';old['evidence'].append('https://www.arhivyu.rs/en/leksikon-jugoslavije/konstitutivni_akti_jugoslavije');extra.append(old) f['from']=1963;f['fromDate']='1963-04-07' if f['entity']=='Q83286' and f['to']<1963:f['name']='Föderative Volksrepublik Jugoslawien' if f['entity']=='Q15180' and f['from']==1991: f['untilDate']='1991-12-26';f['note']='Jahresgeometrie bildet nicht alle unterjährigen Austritte ab. Ab der formellen Auflösung am 26.12.1991 nicht fortgeschrieben; Nachfolgedaten können fehlen.';f['evidence'].append('https://www.bush41library.gov/digital-research-room/finding-aid/foia/records-dissolution-soviet-union') features.extend(extra) manifest={'version':3,'minYear':-3000,'maxYear':2026,'sourceMaxYear':2024,'retrieved':'2026-11-03','source':URL,'sourceVersion':'Cliopatria v0.2.1','commit':SHA,'license':'CC BY 4.0','archiveSha256':hashlib.sha256((R/'cliopatria.geojson.zip').read_bytes()).hexdigest(),'timeConvention':'FromYear/ToYear inclusive; fromDate inclusive/untilDate exclusive; modern yearly reference 31 December, ongoing year today','chunks':[]} def write_chunk(lo,hi): subset=[f for f in features if f['from']<=hi and f['to']>=lo] b=json.dumps({'features':subset},ensure_ascii=False,separators=(',',':')).encode() if len(b)>1500000 and hi>lo: mid=(lo+hi)//2;write_chunk(lo,mid);write_chunk(mid+1,hi);return key=('bc'+str(-lo) if lo<0 else str(lo))+'-'+('bc'+str(-hi) if hi<0 else str(hi)) asset='pelasgia-atlas-v3-period-'+key+'.json';(A/asset).write_bytes(b) manifest['chunks'].append({'from':lo,'to':hi,'asset':asset,'count':len(subset),'sha256':hashlib.sha256(b).hexdigest()});print(asset,len(b),len(subset),flush=True) for lo in range(-3000,2001,500):write_chunk(lo,2024 if lo==2000 else lo+499) (A/'pelasgia-atlas-v3-manifest.json').write_text(json.dumps(manifest,ensure_ascii=False,separators=(',',':')),encoding='utf-8');(R/'features-readable.json').write_text(json.dumps(features,ensure_ascii=False),encoding='utf-8');print('Imported',len(features),'features')