| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126 |
- // Temporary QA overlay mounted only for ?pipeVerify=1 on the shared cockpit route.
- // It exercises the actual scene; it is removed from mapService before delivery.
- import { Cartesian3, Cartographic, HeadingPitchRange, sampleTerrainMostDetailed } from 'cesium'
- import { toPipeNetwork } from '../../src/modules/cockpit/domain/pipeNetwork.js'
- import { pipeColorKey } from '../../src/modules/cockpit/domain/pipeVisual.js'
- export function mountPipeAcceptance({ viewer, host, setViewMode, applyViewPreset }) {
- const params=new URLSearchParams(location.search)
- const originalFetch=window.fetch.bind(window),missingKind=params.get('pipeMissing')
- if (['valve','elbow','branch'].includes(missingKind)) {
- window.fetch=(input,options)=>String(input).endsWith(`/models/pipes/${missingKind}.gltf`)
- ? Promise.resolve(new Response('isolated missing template',{status:404})) : originalFetch(input,options)
- }
- const panel = document.createElement('aside')
- panel.id = 'pipe-acceptance-overlay'
- panel.style.cssText = 'position:fixed;left:25%;bottom:12px;z-index:1200;max-width:55vw;max-height:36vh;overflow:auto;padding:10px;background:#04121feb;color:white;font:13px monospace;border:1px solid #32c5ff'
- panel.innerHTML = '<b>管体验收(开发隔离控件)</b><div></div><pre style="white-space:pre-wrap"></pre>'
- const buttons = panel.querySelector('div'), output = panel.querySelector('pre')
- let network, target, range = 160, heading = 0
- const results = {}
- const emit = (key, value) => { results[key] = value; output.textContent = JSON.stringify(results, null, 2) }
- const add = (name, action) => {
- const button = document.createElement('button')
- button.textContent = name; button.style.cssText = 'margin:3px;padding:5px;color:#fff;background:#12426a;border:1px solid #328abc'
- button.onclick = () => Promise.resolve(action()).catch(error => emit('error', String(error.message || error)))
- buttons.appendChild(button)
- }
- const pipes = async () => {
- if (!network) {
- const config = await fetch('/cockpit/config.json').then(r => r.json())
- const payload = await fetch(config.api.baseUrl + '/infrastructure/geometry').then(r => r.json())
- if (payload.code !== 200) throw new Error('geometry business error')
- network = toPipeNetwork(payload.data)
- }
- return network
- }
- const aim = async (key) => {
- const data = await pipes()
- const row = key === 'MANHOLE' ? data.components.find(p => p.industry === 'MANHOLE') : data.pipes.find(p => p.diameter > 0 && pipeColorKey(p) === key)
- if (!row) throw new Error('No real target ' + key)
- const a = row.position || row.positions[0], b = row.position || row.positions.at(-1)
- const lon = (a[0]+b[0])/2, lat = (a[1]+b[1])/2
- const [point] = await sampleTerrainMostDetailed(viewer.terrainProvider, [Cartographic.fromDegrees(lon,lat)])
- target = {code:row.code,key,lon,lat,height:point.height}
- setViewMode('pipeline'); heading=0
- applyViewPreset({lon,lat,height:point.height},{heading:0,pitch:-45,range},{immediate:true})
- viewer.scene.requestRender()
- emit('target', {...target,range,remark:row.remark||'',canvas:[viewer.canvas.width,viewer.canvas.height]})
- }
- const frame = () => new Promise(resolve => {
- const detach = viewer.scene.postRender.addEventListener(() => { detach(); resolve() })
- viewer.scene.requestRender()
- })
- const capture = () => {
- const canvas=document.createElement('canvas');canvas.width=viewer.canvas.width;canvas.height=viewer.canvas.height
- const ctx=canvas.getContext('2d',{willReadFrequently:true});ctx.drawImage(viewer.canvas,0,0)
- return ctx.getImageData(0,0,canvas.width,canvas.height).data
- }
- const modelList = () => {
- const models=[]
- for(let i=0;i<viewer.scene.primitives.length;i++){const p=viewer.scene.primitives.get(i);if(p.id?.kind==='pipe-model-batch')models.push(p)}
- return models
- }
- add('真实污水近景',()=>aim('SEWAGE'))
- add('真实雨水近景',()=>aim('RAINWATER'))
- add('真实燃气近景',()=>aim('GAS'))
- add('真实供水近景',()=>aim('WATER_SUPPLY'))
- add('窨井紫色',()=>aim('MANHOLE'))
- add('远景800m',async()=>{range=800;await aim(target?.key||'SEWAGE')})
- add('近景160m',async()=>{range=160;await aim(target?.key||'SEWAGE')})
- add('测管体像素',async()=>{
- if(!target)throw new Error('Choose target')
- const source=viewer.dataSources.getByName('cockpit-pipelines')[0],shown=source.show
- const models=modelList(),states=models.map(m=>m.show)
- source.show=false;models.forEach(m=>m.show=false)
- try{
- await frame();await frame();const before=capture()
- models.forEach((m,i)=>m.show=states[i]);await frame();await frame();const after=capture()
- const count={GAS:0,WATER_SUPPLY:0,SEWAGE:0,RAINWATER:0};let changed=0
- for(let i=0;i<after.length;i+=4){const r=after[i],g=after[i+1],b=after[i+2];if(Math.abs(r-before[i])+Math.abs(g-before[i+1])+Math.abs(b-before[i+2])<35)continue;changed++
- if(r>120&&r>g*1.7&&r>b*1.35)count.GAS++
- if(b>120&&b>r*1.7&&b>g*1.15)count.WATER_SUPPLY++
- if(g>120&&g>r*1.7&&g>b*1.3)count.SEWAGE++
- if(g>120&&b>120&&r<100&&Math.abs(g-b)<90)count.RAINWATER++}
- emit('pixels',{modelOnly:true,backgroundSubtracted:true,target:target.code,color:target.key,counts:count,changed,pass:(count[target.key]||0)>=80,canvas:[viewer.canvas.width,viewer.canvas.height]})
- }finally{models.forEach((m,i)=>m.show=states[i]);source.show=shown;viewer.scene.requestRender()}
- })
- add('原生rAF',()=>{
- const start=performance.now(),times=[]
- const next=t=>{times.push(t);if(t-start<5000){requestAnimationFrame(next);return}const secs=(times.at(-1)-times[0])/1000;emit('raf',{frames:times.length,seconds:secs,fps:(times.length-1)/secs,visible:document.visibilityState,focused:document.hasFocus()})}
- requestAnimationFrame(next)
- })
- add('4K运动FPS',()=>{
- if(!target)throw new Error('Choose target')
- const scene=viewer.scene,old=scene.requestRenderMode,start=performance.now(),times=[];let hidden=false,lostFocus=false
- scene.requestRenderMode=false
- const detach=scene.postRender.addEventListener(()=>{
- const now=performance.now();times.push(now);hidden ||= document.visibilityState!=='visible';lostFocus ||= !document.hasFocus()
- if(now-start<10000){heading=(now-start)/10000*Math.PI/4;applyViewPreset({lon:target.lon,lat:target.lat,height:target.height},{heading:heading*180/Math.PI,pitch:-45,range},{immediate:true});return}
- detach();scene.requestRenderMode=old
- const gaps=times.slice(1).map((t,i)=>t-times[i]).sort((a,b)=>a-b),secs=(times.at(-1)-times[0])/1000
- const gl=viewer.canvas.getContext('webgl2')||viewer.canvas.getContext('webgl'),debug=gl.getExtension('WEBGL_debug_renderer_info')
- emit('fps',{frames:times.length,seconds:secs,fps:(times.length-1)/secs,p95:gaps[Math.floor(gaps.length*.95)],max:gaps.at(-1),canvas:[viewer.canvas.width,viewer.canvas.height],hidden,lostFocus,renderer:debug?gl.getParameter(debug.UNMASKED_RENDERER_WEBGL):gl.getParameter(gl.RENDERER),pass:(times.length-1)/secs>=30})
- })
- })
- add('隔离四类样例',async()=>{
- const [{CustomDataSource,Color,HeightReference},{createCesiumPipeModels}]=await Promise.all([import('cesium'),import('../../src/modules/cockpit/services/cesiumPipeModels.js')])
- const lon=110.39028,lat=28.44368
- const [ground]=await sampleTerrainMostDetailed(viewer.terrainProvider,[Cartographic.fromDegrees(lon,lat)])
- const example={pipes:[{code:'ISOLATED-SPLINE',industry:'GAS',diameter:300,positions:[[lon-.00012,lat],[lon-.00005,lat],[lon,lat+.00005]]}],components:[
- {code:'ISOLATED-ELBOW',kind:'elbow',industry:'GAS',position:[lon+.00004,lat]},
- {code:'ISOLATED-VALVE',kind:'valve',industry:'GAS',position:[lon+.00007,lat]},
- {code:'ISOLATED-TEE',kind:'branch',label:'tee',industry:'GAS',position:[lon+.0001,lat]},
- {code:'ISOLATED-CROSS',kind:'branch',label:'cross',industry:'GAS',position:[lon+.00013,lat]}]}
- const source=new CustomDataSource('pipe-isolated-acceptance')
- for(const row of example.components)source.entities.add({id:'component:'+row.code,position:Cartesian3.fromDegrees(...row.position),point:{pixelSize:14,color:Color.RED,heightReference:HeightReference.CLAMP_TO_GROUND,disableDepthTestDistance:Infinity},label:{text:row.kind+'(隔离样例)',heightReference:HeightReference.CLAMP_TO_GROUND,disableDepthTestDistance:Infinity}})
- await viewer.dataSources.add(source)
- const layer=createCesiumPipeModels(viewer,{source,onChange:s=>emit('examples',{isolated:true,missingKind,instances:s.instances,batches:s.batches,pending:s.pending,failed:s.failed,fallback:s.fallback,visibleSymbols:source.entities.values.filter(e=>e.show).length,byKind:s.byKind})})
- setViewMode('pipeline');applyViewPreset({lon,lat,height:ground.height},{heading:0,pitch:-45,range:60},{immediate:true})
- layer.sync(example)
- results.isolated=true
- })
- add('收起诊断',()=>panel.hidden=true)
- host.appendChild(panel)
- return {destroy:()=>panel.remove()}
- }
|