Files
mcm-26-b/scripts/build_q1_demo.py
2026-09-12 01:56:34 +08:00

80 lines
3.7 KiB
Python

"""Build reproducible examples and the interactive visualization from Python results."""
from dataclasses import asdict
import json
import math
from pathlib import Path
from mcm26b import Measurement, solve
ROOT = Path(__file__).resolve().parents[1]
ASSETS = Path(__file__).resolve().parent / "assets"
def measured(stations, target, errors):
return [Measurement(x,y,(math.degrees(math.atan2(target[1]-y,target[0]-x))+e)%360)
for (x,y),e in zip(stations,errors)]
def scenarios():
target=(80,90)
normal=measured([(-600,-250),(650,-100),(100,700)],target,[.65,-.55,.35])
h=20*math.sqrt(3)
triangle=[Measurement(-1000,0,1),Measurement(540,-500*math.sqrt(3),121),
Measurement(520,h+500*math.sqrt(3),241)]
return [
dict(id='normal',name='三点交会',source='synthetic',target=target,measurements=normal),
dict(id='triangle',name='直径圆反例',source='synthetic',target=(20,h/3),measurements=triangle),
dict(id='poor',name='近共线检测点',source='synthetic',target=(900,50),
measurements=measured([(0,0),(100,0),(200,0)],(900,50),[0,0,0])),
dict(id='empty',name='不相容观测',source='synthetic',target=None,
measurements=[Measurement(-100,0,180),Measurement(100,0,0)]),
dict(id='segment',name='零误差退化线段',source='synthetic',target=(40,0),
measurements=[Measurement(0,0,0,0),Measurement(100,0,180,0)]),
]
def main():
cases=scenarios()
practice=ROOT/'output/q1/practice'
for path in sorted(practice.glob('*_observations.json')):
doc=json.loads(path.read_text(encoding='utf-8'))
cases.append(dict(id='practice-'+doc['case_code'],name='官方演练 '+doc['case_code'],
source='official_practice',target=None,
measurements=[Measurement(**m) for m in doc['measurements']]))
examples=ROOT/'examples/q1'
results=ROOT/'output/q1'
examples.mkdir(parents=True,exist_ok=True)
results.mkdir(parents=True,exist_ok=True)
dataset=[]
for case in cases:
ms=case['measurements']
frames=[solve(ms[:n]) for n in range(1,len(ms)+1)]
data={**case,'measurements':[asdict(m) for m in ms],'frames':frames}
dataset.append(data)
if case['source']=='synthetic':
(examples/(case['id']+'.json')).write_text(
json.dumps({k:v for k,v in data.items() if k!='frames'},
ensure_ascii=False,indent=2),encoding='utf-8')
(results/(case['id']+'_result.json')).write_text(
json.dumps(frames[-1],ensure_ascii=False,indent=2,allow_nan=False),encoding='utf-8')
(results/'demo_data.json').write_text(
json.dumps(dataset,ensure_ascii=False,indent=2,allow_nan=False),encoding='utf-8')
template=(ASSETS / 'q1.template.html').read_text(encoding='utf-8')
fragment=template.replace('/*__Q1_DATA__*/',
json.dumps(dataset,ensure_ascii=False,allow_nan=False))
(ROOT/'output'/'q1'/'question1_full.html').write_text(fragment,encoding='utf-8')
# A standalone companion uses the same fragment and computation results.
theme=(ASSETS / 'standalone.css').read_text(encoding='utf-8')
page=('<!doctype html><html lang="zh-CN"><meta charset="utf-8">'
'<meta name="viewport" content="width=device-width,initial-scale=1">'
'<title>B题问题一交会定位</title>'
'<style>'+theme+'</style><body>'+fragment+'</body></html>')
(results/'question1.html').write_text(page,encoding='utf-8')
print(json.dumps({'cases':len(dataset),'fragment_bytes':len(fragment.encode('utf-8')),
'standalone':str(results/'question1.html')},ensure_ascii=False))
if __name__=='__main__':
main()