56 lines
4.4 KiB
Python
56 lines
4.4 KiB
Python
"""Add a small, explicitly scoped set of detailed puzzles from local map inputs.
|
|
|
|
Run after prepare-maps.py. Raw downloads stay in ignored data/, while the
|
|
compact source index is required by the build-time geographic audit.
|
|
"""
|
|
import copy, hashlib, json, pathlib, subprocess
|
|
from map_geometry import make_level, place_labels
|
|
ROOT=pathlib.Path(__file__).resolve().parent.parent
|
|
source=ROOT/'src/maps.js'
|
|
data=json.loads(source.read_text().removeprefix('window.MAP_DATA=').strip().removesuffix(';'))
|
|
# Preserve the national and 27 provincial levels, including province-administered peers.
|
|
data['levels']=[lv for lv in data['levels'] if lv.get('kind') not in ('county','partition','county-city')]
|
|
for lv in data['levels']:
|
|
lv['kind']='province' if lv['id']=='100000' else 'prefecture'
|
|
index={'downloadDate':'2026-10-17','levels':[]}
|
|
expected={'110000':('北京市',16),'120000':('天津市',16),'310000':('上海市',16),'500000':('重庆市',38),'810000':('香港特别行政区',18),'820000':('澳门特别行政区',8)}
|
|
for code,(name,count) in expected.items():
|
|
raw=ROOT/'data/provinces-raw'/(code+'.json')
|
|
features=json.loads(raw.read_text())['features']
|
|
features=[f for f in features if f['properties'].get('level')=='district']
|
|
assert len(features)==count,(code,len(features))
|
|
lv=make_level(features,code,name,code)
|
|
assert lv and len(lv['regions'])==count
|
|
lv.pop('projection',None);lv['kind']='partition' if code in ('810000','820000') else 'county'
|
|
lv['playLabel']='分区拼图' if lv['kind']=='partition' else '县区拼图'
|
|
lv['unitCaption']='按素材分区 · 堂区及路凼填海区' if code=='820000' else '18个分区' if code=='810000' else '区 · 县 · 自治县' if code=='500000' else '市辖区'
|
|
lv['source']='DataV.GeoAtlas';place_labels(lv);data['levels'].append(lv)
|
|
index['levels'].append({'id':code,'name':name,'kind':lv['kind'],'source':'DataV.GeoAtlas','sha256':hashlib.sha256(raw.read_bytes()).hexdigest(),'regions':[{'id':str(f['properties']['adcode']),'name':f['properties']['name'],'rings':len(f['geometry']['coordinates']) if f['geometry']['type']=='MultiPolygon' else 1,'holes':sum(len(poly)-1 for poly in f['geometry']['coordinates']) if f['geometry']['type']=='MultiPolygon' else len(f['geometry']['coordinates'])-1} for f in features]})
|
|
# An explicitly attributed county/city source is separate from the national map.
|
|
taiwan=ROOT/'data/taiwan-raw/counties.geojson'
|
|
if not taiwan.exists():raise SystemExit('缺少台湾县市构建输入,请先运行 scripts/prepare-taiwan.py')
|
|
features=json.loads(taiwan.read_text())['features']
|
|
assert len(features)==20
|
|
play_features=copy.deepcopy(features)
|
|
# Keep every source exterior ring. Two remote island clusters are translated
|
|
# into a labelled inset area so one county does not span thousands of km.
|
|
insets=0
|
|
for f in play_features:
|
|
for poly in f['geometry']['coordinates']:
|
|
ring=poly[0];cx=sum(p[0] for p in ring)/len(ring);cy=sum(p[1] for p in ring)/len(ring)
|
|
if cy<21 or cx<118:
|
|
target=(118.95,21.7) if cy<15 else (119.35,21.7)
|
|
dx,dy=target[0]-cx,target[1]-cy
|
|
for p in ring:p[0]+=dx;p[1]+=dy
|
|
insets+=1
|
|
assert insets>0
|
|
lv=make_level(play_features,'710000','台湾省','710000');lv.pop('projection',None)
|
|
lv.update(kind='county-city',playLabel='县市拼图',unitCaption='县市 · 远海岛屿移位示意',source='NLSC',scopeNote='县市关卡包括台湾岛及澎湖;远海岛屿采用移位示意。金门、连江属福建,不重复列入。')
|
|
place_labels(lv);data['levels'].append(lv)
|
|
index['levels'].append({'id':'710000','name':'台湾省','kind':'county-city','source':'NLSC','snapshot':'COUNTY_MOI_1090820','translatedOffshoreRings':insets,'sourceURL':'https://data.gov.tw/dataset/7442','license':'政府资料开放授权条款第1版','sha256':hashlib.sha256(taiwan.read_bytes()).hexdigest(),'regions':[{'id':str(f['properties']['adcode']),'name':f['properties']['name'],'rings':len(f['geometry']['coordinates']),'holes':sum(len(poly)-1 for poly in f['geometry']['coordinates'])} for f in features]})
|
|
assert len(data['levels'])==35
|
|
source.write_text('window.MAP_DATA='+json.dumps(data,ensure_ascii=False,separators=(',',':'))+';\n')
|
|
subprocess.run(['node',str(ROOT/'scripts/triangulate-holes.mjs')],check=True,cwd=ROOT)
|
|
(ROOT/'research/detail-map-index.json').write_text(json.dumps(index,ensure_ascii=False,indent=2)+'\n')
|
|
print('Detailed levels:',[(lv['name'],lv['playLabel'],len(lv['regions'])) for lv in data['levels'] if lv['kind'] not in ('province','prefecture')])
|