feat: sync Shanhe 2.2 source and add remote package verification
Shanhe source and offline package checks / verify (pull_request) Failing after 5s
Shanhe source and offline package checks / verify (pull_request) Failing after 5s
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
"""Build-time geometry simplification shared by prefecture and detailed maps."""
|
||||
import math
|
||||
def area(r):return abs(sum(r[i][0]*r[(i+1)%len(r)][1]-r[(i+1)%len(r)][0]*r[i][1] for i in range(len(r)))/2)
|
||||
def dist(p,a,b):
|
||||
dx,dy=b[0]-a[0],b[1]-a[1];n=dx*dx+dy*dy
|
||||
t=max(0,min(1,((p[0]-a[0])*dx+(p[1]-a[1])*dy)/n)) if n else 0
|
||||
return (p[0]-a[0]-t*dx)**2+(p[1]-a[1]-t*dy)**2
|
||||
def dp(p,eps):
|
||||
if len(p)<3:return p
|
||||
v,i=max((dist(p[k],p[0],p[-1]),k) for k in range(1,len(p)-1))
|
||||
if v<=eps*eps:return [p[0],p[-1]]
|
||||
return dp(p[:i+1],eps)[:-1]+dp(p[i:],eps)
|
||||
def project(p):return [p[0]*.82,-math.log(math.tan(math.pi/4+math.radians(p[1])/2))*180/math.pi*.82]
|
||||
def make_level(features,code,name,province):
|
||||
fs=[f for f in features if f.get('properties',{}).get('name') and f['properties'].get('adcode')]
|
||||
if len(fs)<2:return None
|
||||
reg=[];boundary=[]
|
||||
for f in fs:
|
||||
prop=f['properties'];g=f['geometry'];polys=g['coordinates'] if g['type']=='MultiPolygon' else [g['coordinates']]
|
||||
rings=[project_ring(p[0]) for p in polys if len(p[0])>=4]
|
||||
holes=[project_ring(hole) for poly in polys for hole in poly[1:] if len(hole)>=4]
|
||||
rings.sort(key=area,reverse=True)
|
||||
if not rings:continue
|
||||
main=rings[0];boundary.append({(round(x,3),round(y,3)) for r in rings+holes for x,y in r})
|
||||
# Keep island geometry, simplify each independently; no invented boundaries.
|
||||
span=max(max(p[0] for p in main)-min(p[0] for p in main),max(p[1] for p in main)-min(p[1] for p in main))
|
||||
simple=[]
|
||||
for r in rings:
|
||||
localspan=max(max(p[0] for p in r)-min(p[0] for p in r),max(p[1] for p in r)-min(p[1] for p in r))
|
||||
eps=max(min(span*.005,localspan*.01),.00000001);q=dp(r,eps)
|
||||
while len(q)>90:eps*=1.4;q=dp(r,eps)
|
||||
if len(q)<4:q=r
|
||||
simple.append(q[:-1] if q[0]==q[-1] else q)
|
||||
cx=sum(x for x,y in main[:-1])/max(1,len(main)-1);cy=sum(y for x,y in main[:-1])/max(1,len(main)-1)
|
||||
entry={'id':str(prop['adcode']),'name':prop['name'],'rings':simple,'center':[cx,cy]}
|
||||
if holes:
|
||||
entry['holes']=[]
|
||||
for hole in holes:
|
||||
eps=span*.001;q=dp(hole,eps)
|
||||
while len(q)>90:eps*=1.4;q=dp(hole,eps)
|
||||
if len(q)<4:q=hole
|
||||
entry['holes'].append(q[:-1] if q[0]==q[-1] else q)
|
||||
reg.append(entry)
|
||||
if len(reg)<2:return None
|
||||
edges=[]
|
||||
for i in range(len(reg)):
|
||||
for j in range(i):
|
||||
if len(boundary[i]&boundary[j])>=2:edges.append([j,i,0])
|
||||
# Join disconnected island components with explicit game-only sea routes.
|
||||
def components():
|
||||
groups=[{i} for i in range(len(reg))]
|
||||
for a,b,_ in edges:
|
||||
ga=next(g for g in groups if a in g);gb=next(g for g in groups if b in g)
|
||||
if ga is not gb:ga.update(gb);groups.remove(gb)
|
||||
return groups
|
||||
while len(components())>1:
|
||||
gs=components();candidates=[]
|
||||
for a in gs[0]:
|
||||
for g in gs[1:]:
|
||||
for b in g:
|
||||
pa,pb=reg[a]['center'],reg[b]['center'];candidates.append(((pa[0]-pb[0])**2+(pa[1]-pb[1])**2,a,b))
|
||||
_,a,b=min(candidates);edges.append([a,b,1])
|
||||
allp=[p for r in reg for ring in r['rings'] for p in ring]
|
||||
minx,miny=min(p[0] for p in allp),min(p[1] for p in allp);maxx,maxy=max(p[0] for p in allp),max(p[1] for p in allp)
|
||||
span=max(maxx-minx,maxy-miny);scale=1000/span
|
||||
for r in reg:
|
||||
r['center']=[round((r['center'][0]-minx)*scale,2),round((r['center'][1]-miny)*scale,2)]
|
||||
r['rings']=[[[round((x-minx)*scale,4),round((y-miny)*scale,4)] for x,y in ring] for ring in r['rings']]
|
||||
if r.get('holes'):r['holes']=[[[round((x-minx)*scale,4),round((y-miny)*scale,4)] for x,y in ring] for ring in r['holes']]
|
||||
return {'id':str(code),'name':name,'province':str(province),'width':round((maxx-minx)*scale,2),'height':round((maxy-miny)*scale,2),'regions':reg,'edges':edges,'projection':[minx,miny,scale]}
|
||||
def project_ring(r):return [project(p) for p in r]
|
||||
|
||||
def inside(p,ring):
|
||||
x,y=p;hit=False
|
||||
for i,a in enumerate(ring):
|
||||
b=ring[(i+1)%len(ring)]
|
||||
if (a[1]>y)!=(b[1]>y) and x<(b[0]-a[0])*(y-a[1])/(b[1]-a[1])+a[0]:hit=not hit
|
||||
return hit
|
||||
|
||||
def place_labels(lv):
|
||||
for r in lv['regions']:
|
||||
ring=r['rings'][0];minx,maxx=min(p[0] for p in ring),max(p[0] for p in ring);miny,maxy=min(p[1] for p in ring),max(p[1] for p in ring)
|
||||
candidates=[[minx+(maxx-minx)*i/14,miny+(maxy-miny)*j/14] for i in range(1,14) for j in range(1,14)]
|
||||
candidates=[p for p in candidates if inside(p,ring) and not any(inside(p,hole) for hole in r.get('holes',[]))]
|
||||
if candidates:r['label']=max(candidates,key=lambda p:min(dist(p,ring[k],ring[(k+1)%len(ring)]) for k in range(len(ring))))
|
||||
else:r['label']=r['center']
|
||||
r['label']=[round(v,3) for v in r['label']]
|
||||
|
||||
|
||||
def hainan_offshore_inset(lv):
|
||||
"""Gameplay inset: preserve every Sansha ring, uniformly scale and translate.
|
||||
|
||||
The national map and raw coordinates are untouched. Only this provincial
|
||||
puzzle uses a clearly labelled inset so the main island remains playable.
|
||||
"""
|
||||
if lv['id']!='460000':return
|
||||
offshore=next(r for r in lv['regions'] if r['id']=='460300')
|
||||
for r in lv['regions']:
|
||||
r['labelName']=r['name'].split('黎族')[0] if '黎族' in r['name'] else r['name'].removesuffix('市').removesuffix('县')
|
||||
mainland=[p for r in lv['regions'] if r is not offshore for ring in r['rings'] for p in ring]
|
||||
sea=[p for ring in offshore['rings'] for p in ring]
|
||||
minx,maxx=min(p[0] for p in mainland),max(p[0] for p in mainland)
|
||||
miny,maxy=min(p[1] for p in mainland),max(p[1] for p in mainland)
|
||||
sx,sy=min(p[0] for p in sea),min(p[1] for p in sea)
|
||||
sw,sh=max(p[0] for p in sea)-sx,max(p[1] for p in sea)-sy
|
||||
width,height=maxx-minx,maxy-miny
|
||||
factor=min(width*.38/sw,height*.55/sh)
|
||||
tx,ty=maxx+width*.10,maxy-height*.53
|
||||
move=lambda p:[tx+(p[0]-sx)*factor,ty+(p[1]-sy)*factor]
|
||||
offshore['rings']=[[move(p) for p in ring] for ring in offshore['rings']]
|
||||
if offshore.get('holes'):offshore['holes']=[[move(p) for p in ring] for ring in offshore['holes']]
|
||||
offshore['center']=move(offshore['center'])
|
||||
allp=[p for r in lv['regions'] for ring in r['rings'] for p in ring]
|
||||
bx,by=min(p[0] for p in allp),min(p[1] for p in allp)
|
||||
bw,bh=max(p[0] for p in allp)-bx,max(p[1] for p in allp)-by
|
||||
scale=1000/max(bw,bh)
|
||||
normalize=lambda p:[round((p[0]-bx)*scale,4),round((p[1]-by)*scale,4)]
|
||||
for r in lv['regions']:
|
||||
r['rings']=[[normalize(p) for p in ring] for ring in r['rings']]
|
||||
if r.get('holes'):r['holes']=[[normalize(p) for p in ring] for ring in r['holes']]
|
||||
r['center']=normalize(r['center'])
|
||||
lv['width'],lv['height']=round(bw*scale,2),round(bh*scale,2)
|
||||
# Reserve space around the islands, for a visible frame and explanatory label.
|
||||
a,b=normalize([tx-width*.025,ty-height*.025]),normalize([tx+sw*factor+width*.025,ty+sh*factor+height*.025])
|
||||
lv['mapInsets']=[{'id':'460300','label':'三沙诸岛 · 缩放移位示意','x':a[0],'y':a[1],'width':b[0]-a[0],'height':b[1]-a[1]}]
|
||||
lv['scopeNote']='含15个省直辖地区;三沙诸岛缩放移位示意。'
|
||||
lv['unitCaption']='地级及省直辖 · 三沙移位示意'
|
||||
lv['projectionNote']='海南省域游戏关卡使用三沙诸岛缩放移位示意,全国地图与源素材坐标保持原状。'
|
||||
Reference in New Issue
Block a user