acceptance.html 6.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. <!doctype html>
  2. <html lang="zh-CN"><head><meta charset="utf-8"><title>管道模型隔离验收(非业务数据)</title>
  3. <style>html,body,#map{margin:0;width:100%;height:100%;overflow:hidden;background:#04121f;color:white;font:16px sans-serif}aside{position:absolute;top:8px;left:8px;z-index:5;background:#04121fe8;padding:12px;max-width:90vw}button{margin:4px;padding:8px}pre{white-space:pre-wrap;margin:4px;font:12px monospace;max-height:24vh;overflow:auto}</style></head>
  4. <body><div id="map"></div><aside><strong>隔离验收:真实数据与构件样例分开,不修改业务接口/统计</strong><br>
  5. <button id="examples">四类示意件</button><button id="real">真实几何</button><button id="stress">隔离压力样例(虚拟管径)</button><button id="ground">地面视图</button><button id="underground">地下视图</button><button id="fps">采样 10 秒真实渲染 FPS</button><button id="clear">清理</button>
  6. <pre id="state">加载引擎</pre><pre id="performance"></pre></aside>
  7. <script type="module">
  8. window.CESIUM_BASE_URL='/cesium/'
  9. const missingKind=new URLSearchParams(location.search).get('missing')
  10. const originalFetch=window.fetch.bind(window)
  11. if(['spline','elbow','valve','branch','cross'].includes(missingKind)) {
  12. window.fetch=(input,options)=>String(input).endsWith(`/models/pipes/${missingKind}.gltf`)?Promise.resolve(new Response('isolated missing asset',{status:404})):originalFetch(input,options)
  13. }
  14. const { Cartesian3, HeadingPitchRange, Color }=await import('/.cache/deps/cesium.js')
  15. const { createViewer, applyTerrain }=await import('/src/modules/cockpit/services/cesiumEngine.js')
  16. const { syncPipeLayer, clearPipeLayer }=await import('/src/modules/cockpit/services/cesiumPipeLayer.js')
  17. const { toPipeNetwork }=await import('/src/modules/cockpit/domain/pipeNetwork.js')
  18. const config=await fetch('/cockpit/config.json').then(r=>r.json())
  19. const viewer=createViewer(document.querySelector('#map'),{ready:true,kind:'tms',url:'Assets/Textures/NaturalEarthII'})
  20. await applyTerrain(viewer,{ready:true,url:config.map.terrain.url,requestVertexNormals:true})
  21. let network={pipes:[],components:[]}
  22. viewer.canvas.setAttribute('aria-label','管道模型验收画面')
  23. viewer.canvas.setAttribute('role','img')
  24. let focus={lon:config.map.home.lon,lat:config.map.home.lat,height:100,range:config.map.home.range}
  25. const paint=(state)=>{const {covered=[],...counts}=state;document.querySelector('#state').textContent=JSON.stringify({mode:document.body.dataset.mode,missingAsset:missingKind,width:viewer.canvas.width,height:viewer.canvas.height,pipes:network.pipes.length,components:network.components.length,coveredObjects:covered.length,...counts},null,2);document.body.dataset.instances=state.instances;document.body.dataset.failed=state.failed;document.body.dataset.fallback=state.fallback;document.body.dataset.pending=state.pending}
  26. const draw=()=>syncPipeLayer(viewer,network,{onModelChange:paint})
  27. const look=(lon,lat,height,range)=>{focus={lon,lat,height,range};viewer.camera.lookAt(Cartesian3.fromDegrees(lon,lat,height),new HeadingPitchRange(0,-Math.PI/5,range))}
  28. const underground=()=>{viewer.scene.globe.translucency.enabled=true;viewer.scene.globe.translucency.frontFaceAlpha=.2;viewer.scene.globe.translucency.backFaceAlpha=.2;viewer.scene.requestRender()}
  29. document.querySelector('#examples').onclick=async()=>{
  30. document.body.dataset.mode='isolated-four-classes'
  31. const lon=110.39028,lat=28.44368
  32. network={pipes:[{code:'DEMO-SPLINE',industry:'GAS',diameter:600,positions:[[lon-.00012,lat],[lon-.00005,lat],[lon,lat+.00005]]}],components:[
  33. {code:'DEMO-ELBOW',kind:'elbow',label:'elbow',industry:'GAS',position:[lon+.00004,lat]},
  34. {code:'DEMO-VALVE',kind:'valve',label:'valve',industry:'GAS',position:[lon+.00007,lat]},
  35. {code:'DEMO-TEE',kind:'branch',label:'tee',industry:'GAS',position:[lon+.0001,lat]},
  36. {code:'DEMO-CROSS',kind:'branch',label:'cross',industry:'GAS',position:[lon+.00013,lat]},
  37. {code:'DEMO-UNKNOWN',kind:'other',label:'regulator',industry:'GAS',position:[lon+.00017,lat]}]}
  38. const {Cartographic,sampleTerrainMostDetailed}=await import('/.cache/deps/cesium.js')
  39. const [point]=await sampleTerrainMostDetailed(viewer.terrainProvider,[Cartographic.fromDegrees(lon,lat)])
  40. look(lon,lat,point.height,50);underground();await draw()
  41. }
  42. document.querySelector('#real').onclick=async()=>{
  43. document.body.dataset.mode='real-geometry'
  44. const response=await fetch(config.api.baseUrl+'/infrastructure/geometry').then(r=>r.json())
  45. network=toPipeNetwork(response.data)
  46. look(config.map.home.lon,config.map.home.lat,100,config.map.home.range);underground();await draw()
  47. }
  48. document.querySelector('#stress').onclick=async()=>{
  49. document.body.dataset.mode='isolated-stress-virtual-400mm'
  50. const response=await fetch(config.api.baseUrl+'/infrastructure/geometry').then(r=>r.json())
  51. const real=toPipeNetwork(response.data)
  52. network={pipes:real.pipes.map((pipe)=>({...pipe,diameter:400})),components:[]}
  53. look(config.map.home.lon,config.map.home.lat,100,config.map.home.range);underground();await draw()
  54. }
  55. document.querySelector('#ground').onclick=()=>{viewer.scene.globe.translucency.enabled=false;viewer.scene.requestRender()}
  56. document.querySelector('#underground').onclick=underground
  57. document.querySelector('#clear').onclick=async()=>{await clearPipeLayer(viewer);paint({instances:0,failed:0,fallback:0,pending:0})}
  58. document.querySelector('#fps').onclick=()=>{
  59. const scene=viewer.scene,old=scene.requestRenderMode
  60. scene.requestRenderMode=false
  61. const start=performance.now(),stamps=[]
  62. const detach=scene.postRender.addEventListener(()=>{
  63. const elapsed=performance.now()-start
  64. stamps.push(performance.now())
  65. if(elapsed<10000){
  66. viewer.camera.lookAt(Cartesian3.fromDegrees(focus.lon,focus.lat,focus.height),new HeadingPitchRange(elapsed/10000*Math.PI/4,-Math.PI/5,focus.range))
  67. return
  68. }
  69. detach();scene.requestRenderMode=old
  70. const seconds=(stamps.at(-1)-stamps[0])/1000,fps=(stamps.length-1)/seconds
  71. const gaps=stamps.slice(1).map((t,i)=>t-stamps[i]).sort((a,b)=>a-b)
  72. const result={mode:document.body.dataset.mode,frames:stamps.length,seconds,fps,p95FrameMs:gaps[Math.floor(gaps.length*.95)],maxFrameMs:gaps.at(-1),motion:'45deg heading / 10s',canvas:[viewer.canvas.width,viewer.canvas.height],viewport:[innerWidth,innerHeight],visibility:document.visibilityState,pass:fps>=30}
  73. document.querySelector('#performance').textContent=JSON.stringify(result,null,2)
  74. document.body.dataset.fps=String(fps)
  75. })
  76. }
  77. look(config.map.home.lon,config.map.home.lat,100,config.map.home.range)
  78. document.querySelector('#state').textContent='引擎已就绪;请选择样例或真实几何'
  79. </script></body></html>