"""GSHHG locator geometry retained; labels adapted for the H01 reader. Derivative geometry provenance and LGPL texts accompany each package. This is physical geography, not a reconstruction of historic boundaries. """ from pathlib import Path from html import escape import json, math from shapely.geometry import Polygon, box from c01_visuals import arrow ROOT=Path(__file__).resolve().parents[1] LAND='#e4e5ca'; SEA='#e6f0ee'; INK='#29473f'; MUTED='#617369' COAST=json.loads((ROOT/'research/g01-geometry/coast-asia.json').read_text()) POLYS=[] for item in COAST: p=Polygon(item['coordinates']) if not p.is_valid:p=p.buffer(0) POLYS.append((p,item['type'])) PLACES={'Nara':(135.805,34.685),'Kyoto':(135.769,35.011),'Kamakura':(139.55,35.32),'Osaka':(135.502,34.69),'Edo / Tokyo':(139.752,35.685),'Hakata':(130.414,33.600),'Nagasaki':(129.875,32.744),'Shuri':(127.719,26.217)} def label(x,y,s,size=33,anchor='start'): return f'{escape(s)}' class Map: def __init__(self,description,bounds,w=640,h=620): self.bounds=bounds; self.w=w; self.h=h l,b,r,t=bounds; self.cos=math.cos(math.radians((b+t)/2)) self.scale=min((w-56)/((r-l)*self.cos),(h-56)/(t-b)) self.ox=(w-(r-l)*self.cos*self.scale)/2;self.oy=(h-(t-b)*self.scale)/2 self.parts=[f'{escape(description)}'] # Clip at the actual canvas, not an inset box surrounded by blue. An # inland data cut must never look like a newly invented coastline. window=box(l-self.ox/(self.cos*self.scale),t-(h-self.oy)/self.scale, l+(w-self.ox)/(self.cos*self.scale),t+self.oy/self.scale) for p,kind in POLYS: q=p.intersection(window) geoms=list(q.geoms) if q.geom_type in ('MultiPolygon','GeometryCollection') else [q] for shape in geoms: if shape.is_empty or shape.geom_type!='Polygon':continue shape=shape.simplify(.006,preserve_topology=True) d=[] for ring in [shape.exterior,*shape.interiors]: points=[self.xy(a,b) for a,b in ring.coords] d.append('M'+' L'.join(f'{x:.2f},{y:.2f}' for x,y in points)+' Z') self.parts.append(f'') self.parts.append(label(w-35,40,'N',30,'middle')+arrow(w-35,101,w-35,54)) def xy(self,lon,lat): l,b,r,t=self.bounds return self.ox+(lon-l)*self.cos*self.scale,self.oy+(t-lat)*self.scale def tag(self,lon,lat,s,x,y,size=33,lines=1,dot=True): px,py=self.xy(lon,lat) end_y=y-size-7 if pyx+width+10: end_x=x+width+9;end_y=y-size*.35 marker=f'' if dot else '' self.parts.append(marker+f''+label(x,y,s,size)) return self def number(self,lon,lat,n,x,y): px,py=self.xy(lon,lat) self.parts.append(f''+label(x,y+11,str(n),33,'middle')) return self def add(self,x,y,s,size=33,anchor='start'): self.parts.append(label(x,y,s,size,anchor));return self def finish(self):return ''.join(self.parts)+'' def build(): out={} m=Map('Five clusters in a physical archipelago: west-central Honshu, eastern Honshu, Kyushu, Hokkaido and the Ryukyu chain. No state borders.',(121,23,147,46),640,720) for lon,lat,n,x,y in [(135.76,34.9,1,362,371),(139.65,35.5,2,545,357),(130.4,33.5,3,157,390),(142.4,43.1,4,530,89),(127.719,26.217,5,124,607)]:m.number(lon,lat,n,x,y) out['clusters']=m.finish() m=Map('The Korean peninsula lies across the straits from southwestern Japan. China lies farther west; the Pacific Ocean is east and southeast of Japan.',(118,24,147,47),640,630) m.tag(127.5,37.4,'Korean',35,120,lines=2).add(35,159,'peninsula').add(55,360,'China',36).tag(138.5,36,'Japan',412,352,36).add(360,489,'Pacific Ocean',34) out['asia']=m.finish() m=Map('Four largest islands: Hokkaido to the north, long Honshu, Shikoku south of western Honshu, Kyushu farther southwest.',(128,30,147,46.2),640,650) m.tag(142.4,43.2,'Hokkaido',334,87,34).tag(138.2,37,'Honshu',403,308,35).tag(133.6,33.7,'Shikoku',285,547,35).tag(130.65,32.4,'Kyushu',28,612,35) out['islands']=m.finish() m=Map('City dots form two clusters: Osaka, Kyoto and Nara in the west; Kamakura and Edo/Tokyo farther east. No region borders.',(134,33,141,37.6),640,455) for name,n,x,y in [('Osaka',1,65,314),('Kyoto',2,190,70),('Nara',3,220,383),('Kamakura',4,453,334),('Edo / Tokyo',5,553,177)]:m.number(*PLACES[name],n,x,y) out['regions']=m.finish() m=Map('Nara is inland east of Osaka Bay and south of Kyoto. Three distinct dots preserve the neighbouring cities rather than merging them.',(134.8,33.7,136.6,35.55),640,615) m.tag(*PLACES['Nara'],'Nara',429,411,38).tag(*PLACES['Kyoto'],'Kyoto',342,129,35).tag(*PLACES['Osaka'],'Osaka',40,261,35).tag(135.15,34.5,'Osaka Bay',38,463,33,dot=False) out['nara']=m.finish() m=Map('Kyoto is west and inland; Kamakura is far east, on the southern coast of the Kanto area. Both centres can matter at the same time.',(134,33.1,141,37.4),640,450) m.tag(*PLACES['Kyoto'],'Kyoto',66,101,36).tag(*PLACES['Kamakura'],'Kamakura',382,381,36) out['kamakura']=m.finish() m=Map('Osaka lies beside its bay. Kyoto is upstream to the northeast. The Seto Inland Sea extends west between Honshu and Shikoku.',(131.6,33.2,136.4,35.6),640,510) m.tag(*PLACES['Osaka'],'Osaka',520,280,35).tag(*PLACES['Kyoto'],'Kyoto',424,70,35).add(230,110,'Honshu',35).add(196,424,'Shikoku',35).tag(133.3,34.1,'Seto Inland',28,186,32,lines=2,dot=False).add(28,225,'Sea',32).tag(135.2,34.5,'Osaka Bay',416,412,32,dot=False) out['osaka']=m.finish() m=Map('A single dot near the head of Tokyo Bay marks Edo, renamed Tokyo in 1868. The name changes; the geographical location does not.',(138.95,34.8,140.45,36.13),640,565) m.tag(*PLACES['Edo / Tokyo'],'Edo / Tokyo',34,130,37).add(*m.xy(139.85,35.46),'Bay',32,'middle') out['edo']=m.finish() m=Map('Hakata is on northern Kyushu. Nagasaki is southwest of it on the western coast. They are different ports, not interchangeable names.',(128.7,30.8,132.5,34.35),640,560) m.tag(*PLACES['Hakata'],'Hakata',285,102,36).tag(*PLACES['Nagasaki'],'Nagasaki',28,357,36).add(358,435,'Kyushu',37) out['kyushu']=m.finish() m=Map('Hokkaido stands between northern Honshu and Sakhalin. Ainu communities participated in a wider northern world; coastlines here do not mark fixed ethnic or state borders.',(138,40,146.7,50),640,680) m.tag(142.4,43.3,'Hokkaido',234,394,36).tag(142.3,48,'Sakhalin',33,104,36).tag(140.7,40.45,'Honshu',35,641,36) out['north']=m.finish() m=Map('The Ryukyu island chain extends southwest from Kyushu toward Taiwan. Shuri lies on Okinawa Island. No historic kingdom boundary is drawn.',(120,23,133,33.6),640,680) m.tag(130.8,32.5,'Kyushu',389,86,36).tag(121,23.9,'Taiwan',26,595,36).tag(*PLACES['Shuri'],'Shuri',432,498,35).add(432,541,'Okinawa',32).add(140,236,'Ryukyu Islands',34) out['south']=m.finish() return out