(function () { "use strict"; const CATEGORIES = { water: { label: "水", title: "水分析", subtitle: "雨洪与水环境", summary: "新数据已经形成地形、建筑淹没深度、364条径流路径和海绵设施方案,可直接开展空间雨洪诊断;排水管网和水质仍需新增数据。", metrics: [["高倒灌风险", "105栋"], ["最大淹没", "0.55m"], ["径流路径", "364条"], ["海绵设施", "669处"]], items: [ ["flood", "海绵内涝风险评估(多维)", "done", "sponge_risk"], ["flood4d", "4D暴雨动态淹没模拟(涨水)", "done", "inundation_sim"], ["flowpath", "地表汇流水动力学脉络", "done", "runoff_streams"], ["retention", "雨洪调蓄需求与缺口", "todo"], ["roof", "建筑屋面径流贡献", "todo"], ["catchment", "地形与汇水分区", "todo"], ["runoff", "综合径流系数", "todo"], ["water_change", "拆违前后径流变化", "todo"], ["drainage", "排水管网承载分析", "todo"], ["water_quality", "水质与河道生态分析", "todo"] ] }, green: { label: "绿", title: "绿色分析", subtitle: "生态与开放空间", summary: "新数据已生成雨水花园、生物沟、下凹绿地和蓄水设施方案,可分析海绵增绿节点;现状绿地、树冠和生态连通仍需独立图层。", metrics: [["雨水花园", "212处"], ["生物沟", "270处"], ["下凹绿地", "82处"], ["方案投资", "900.47万元"]], items: [ ["sponge_green", "海绵微改造规划布局(669处)", "done", "sponge_plan"], ["planning", "经典规划色系(莫兰迪)", "done", "planning"], ["green_potential", "潜在增绿空间", "todo"], ["eco_node", "生态充能节点", "todo"], ["released", "拆违释放空间识别", "todo"], ["green_area", "现状绿地面积统计", "todo"], ["green_layer", "绿地空间图层", "todo"], ["park_service", "公园服务半径", "todo"], ["green_connect", "绿地连通性", "todo"], ["eco_corridor", "生态廊道识别", "todo"], ["canopy", "树冠与遮阴分析", "todo"], ["heat_island", "热岛缓解效应", "todo"], ["biodiversity", "生物多样性分析", "todo"] ] }, people: { label: "人", title: "人口分析", subtitle: "人口活动与空间使用", summary: "建筑级24小时热力已形成,可比较工作日、周末和昼夜活动,并识别日间闲置与时分复用机会。", metrics: [["建筑对象", "369栋"], ["热力时段", "12时段"], ["工作日均值", "41.87"], ["周末均值", "61.85"]], items: [ ["heat24", "24小时时空热力场(动态)", "done", "temporal_heat"], ["weekday", "工作日与周末对比", "todo"], ["daynight", "白天与夜间对比", "todo"], ["building_heat", "建筑级人口热度", "todo"], ["activity_diff", "昼夜活动差异", "todo"], ["day_idle", "日间闲置建筑识别", "todo"], ["sleeping", "睡城特征识别", "todo"], ["time_share", "时分复用潜力", "todo"], ["road_heat", "道路活动热力", "todo"], ["crowd_type", "人群类型识别", "todo"], ["od", "出行OD与活动轨迹", "todo"], ["access", "公共服务可达性", "todo"], ["care_gap", "老幼人群服务缺口", "todo"] ] }, industry: { label: "产", title: "产业分析", subtitle: "产业商业与空间活力", summary: "现有POI与人口热力可用于判断商业供需关系,重点发现高人流低商业和供需双热空间,并提出复用机会。", metrics: [["供需平衡", "236栋"], ["僵尸空心", "85栋"], ["供需双热", "46栋"], ["高人流低商业", "2栋"]], items: [ ["overclocking", "城市超频与供需错配", "done", "overclocking"], ["poi", "商业POI数量", "todo"], ["poi_mix", "商业类型构成", "todo"], ["price", "平均消费价格", "todo"], ["rating", "商户评分分析", "todo"], ["supply", "商业供给指数", "todo"], ["demand", "人流需求指数", "todo"], ["highflow_lowbiz", "高人流低商业识别", "todo"], ["double_hot", "供需双热空间", "todo"], ["balance", "商业供需平衡分析", "todo"], ["reuse", "空间复用潜力", "todo"], ["tidal", "潮汐商业与共享空间", "todo"], ["vacancy", "商铺空置率", "todo"], ["rent", "租金与经营收益", "todo"], ["ownership", "产权与资产分析", "todo"], ["industry_chain", "产业链关联分析", "todo"] ] }, risk: { label: "风险", title: "风险分析", subtitle: "建筑安全与城市韧性", summary: "新数据识别出39栋高风险、329栋中风险和39栋临时建筑;消防间距与应急疏散字段未包含在本次数据中,应调整为待开发。", metrics: [["高风险", "39栋"], ["中风险", "329栋"], ["低风险", "1栋"], ["临时建筑", "39栋"]], items: [ ["risk_level", "结构安全与危房风险", "done", "risk"], ["structure", "建筑楼层与高度分布", "done", "floors"], ["height", "建筑高度分析", "todo"], ["temporary", "临时违章建筑识别", "todo"], ["risk_reason", "建筑风险原因", "todo"], ["fire", "消防间距分析", "todo"], ["nearest", "最近建筑与实际间距", "todo"], ["risk_change", "拆除前后风险对比", "todo"], ["friction", "空间阻碍与摩擦分析", "todo"], ["quality", "建筑质量现场检测", "todo"], ["fire_network", "消防通道网络分析", "todo"], ["evacuation", "应急疏散模拟", "todo"], ["earthquake", "地震与灾害韧性", "todo"], ["monitor", "实时监测与风险预警", "todo"] ] } }; function applyProjectFacts() { const data = window.BUILDING_MODEL_DATA || {}; const buildings = Array.isArray(data.buildings) ? data.buildings : []; const streams = Array.isArray(data.runoff_streams) ? data.runoff_streams : []; const facilities = Array.isArray(data.sponge_facilities) ? data.sponge_facilities : []; const macro = data.macro || {}; const count = (field, value) => buildings.filter(item => String(item[field] ?? "") === value).length; const includes = (field, value) => buildings.filter(item => String(item[field] ?? "").includes(value)).length; const facilityCount = value => facilities.filter(item => `${item.name || ""}${item.type || ""}`.includes(value)).length; const avgHeat = field => { const values = buildings.flatMap(item => Array.isArray(item[field]) ? item[field].map(Number).filter(Number.isFinite) : []); return values.length ? (values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2) : "--"; }; const maxFlood = buildings.reduce((max, item) => Math.max(max, Number(item.inundation_depth_m) || 0), 0).toFixed(2); CATEGORIES.water.metrics = [["高倒灌风险", `${count("sponge_risk_level", "高倒灌风险")}栋`], ["最大淹没", `${maxFlood}m`], ["径流路径", `${streams.length}条`], ["海绵设施", `${facilities.length}处`]]; CATEGORIES.water.summary = `${data.target_dataset || "当前项目"}已形成地形、建筑淹没深度、${streams.length}条径流路径和${facilities.length}处海绵设施方案。`; CATEGORIES.green.metrics = [["雨水花园", `${facilityCount("雨水花园")}处`], ["生物沟", `${facilityCount("生物沟")}处`], ["下凹绿地", `${facilityCount("下凹绿地")}处`], ["设施总量", `${facilities.length}处`]]; CATEGORIES.people.metrics = [["建筑对象", `${buildings.length}栋`], ["热力时段", "12时段"], ["工作日均值", avgHeat("heat_wd")], ["周末均值", avgHeat("heat_we")]]; CATEGORIES.industry.metrics = [["供需平衡", `${includes("mismatch_tag", "平衡")}栋`], ["僵尸空心", `${includes("mismatch_tag", "僵尸")}栋`], ["供需双热", `${includes("mismatch_tag", "双热")}栋`], ["高人流低商业", `${includes("mismatch_tag", "高人流")}栋`]]; CATEGORIES.risk.metrics = [["高风险", `${count("risk_level", "高风险")}栋`], ["中风险", `${count("risk_level", "中风险")}栋`], ["低风险", `${count("risk_level", "低风险")}栋`], ["临时建筑", `${buildings.filter(item => item.is_temporary).length}栋`]]; CATEGORIES.risk.summary = `${data.target_dataset || "当前项目"}共识别${buildings.length}栋建筑;建筑结构、层数和风险字段可直接用于当前诊断。`; Object.values(CATEGORIES).forEach(category => category.metrics = category.metrics.map(([label, value]) => [label, value])); window.ACTIVE_PROJECT_MACRO = macro; } applyProjectFacts(); const theme3d = {sponge_risk:"sponge_risk", sponge_plan:"sponge_plan", inundation_sim:"inundation_sim", runoff_streams:"runoff_streams", planning:"planning", temporal_heat:"temporal_heat", overclocking:"overclocking", floors:"floors", risk:"risk"}; const theme2d = {sponge_risk:"sponge", sponge_plan:"sponge", inundation_sim:"inundation", runoff_streams:"elevation", planning:"base", temporal_heat:"heat", overclocking:"mismatch", floors:"floors", risk:"risk"}; const LEGENDS = { sponge_plan:[["#00e676","微型雨水花园 (495处, 下渗滞留)","point"],["#00e5ff","生物滞留下凹绿地 (45处, 蓄水净化)","point"],["#2979ff","地下调蓄池/沉井 (3处, 核心集水)","point"],["#ffd600","透水铺装嵌草坪 (1处, 渗排结合)","point"]], sponge_risk:[["#ff1744","高倒灌风险 (>25cm水深, 293栋)"],["#ffab00","中度内涝 (10~25cm, 23栋)"],["#00e676","安全/低风险 (<10cm, 60栋)"],["#00e676","海绵规划设施点位 (544处已联动)","point"]], inundation_sim:[["#0055aa","受淹浸没建筑底层"],["#00d2ff","暴雨地表积水面 (2.8m)"],["#e06d63","地势未淹安全建筑"]], runoff_streams:[["#00d2ff","3D地表汇流主干水线 (295条)"],["#1e2e42","地表汇水区建筑"]], planning:[["#e06d63","核心多层街区"],["#f6cb90","次级商业建筑"],["#a0d8b3","外围组团住宅"],["#6a8db5","高层与公共地标"]], temporal_heat:[["#ff1744","高热聚焦点 (>55)"],["#ffd600","活跃中热 (40~55)"],["#00e676","常规流动 (25~40)"],["#00d2ff","低度人流 (<25)"]], overclocking:[["#ff1744","高人流-低商业 (潜力洼地)"],["#00e676","供需双热区 (核心商圈)"],["#ffab00","僵尸空心区 (供需双低)"],["#2b3d54","供需平衡区"]], floors:[["#00e676","1-2层 (低层)"],["#00d2ff","3-4层 (多层)"],["#ffd600","5-6层 (中多层)"],["#ff6d00","7-8层 (高多层)"]], risk:[["#ff1744","高风险危房"],["#ff9100","中风险"],["#00e676","低风险"]] }; let dimension = "risk"; let itemKey = "risk_level"; function enrichData() { const buildings = window.BUILDING_MODEL_DATA?.buildings || []; const byId = new Map(buildings.map(b => [b.id || b.building_id, b])); const source = typeof activeGeoJSON !== "undefined" ? activeGeoJSON : null; (source?.features || []).forEach(feature => { const p = feature.properties || {}; Object.assign(p, byId.get(p.building_id) || {}); }); } function currentItem() { return CATEGORIES[dimension].items.find(item => item[0] === itemKey) || CATEGORIES[dimension].items[0]; } function numeric(value, fallback = 0) { const n = Number(value); return Number.isFinite(n) ? n : fallback; } function heatValue(p) { const values = Array.isArray(p.heat_wd) ? p.heat_wd : (Array.isArray(p.heat_hours) ? p.heat_hours : []); if (values.length) return values.reduce((sum, value) => sum + numeric(value), 0) / values.length; return numeric(p.pop_day, numeric(p.pop_density_day)); } function ramp(value, max, hue = 18) { const ratio = Math.max(0, Math.min(1, max ? value / max : 0)); return `hsl(${hue + (1-ratio)*92} 58% ${72-ratio*30}%)`; } function styleFeature(feature) { const p = feature.properties || {}; const theme = theme2d[currentItem()[3]] || "base"; let fill = "#f7f5ef", color = "#777", weight = 1.1, fillOpacity = .55; if (theme === "risk") fill = ({"高风险":"#d9534f", "中风险":"#f0ad4e", "低风险":"#5b8a60"})[p.risk_level] || "#aaa"; if (theme === "fire") { fill = p.fire_spacing_issue ? "#d9534f" : "#dfe8df"; color = p.fire_spacing_issue ? "#b63f3b" : "#718071"; weight = p.fire_spacing_issue ? 2.6 : 1; } if (theme === "temporary") { fill = p.is_temporary ? "#d9534f" : "#dfe7df"; color = p.is_temporary ? "#a73232" : "#889188"; weight = p.is_temporary ? 3 : 1; } if (theme === "floors") fill = ramp(numeric(p.layers || p.floors), 8, 22); if (theme === "spacing") fill = ramp(Math.max(0, 6-numeric(p.actual_spacing)), 6, 5); if (theme === "roof") fill = ramp(numeric(p.footprint_area), 600, 175); if (theme === "elevation") fill = ramp(numeric(p.elevation_m)-1067, 26, 165); if (theme === "inundation") fill = ramp(numeric(p.inundation_depth_m), .55, 190); if (theme === "sponge") { fill = p.sponge_risk_level === "高倒灌风险" ? "#d9534f" : p.sponge_risk_level === "中度内涝风险" ? "#f0ad4e" : "#5b8a60"; } if (theme === "heat" || theme === "daynight") fill = ramp(heatValue(p), 100, 0); if (theme === "idle") fill = String(p.multiplex_tag || "").includes("日间闲置") ? "#55798b" : "#dfe6df"; if (theme === "sleeping") fill = ramp(numeric(p.sleeping_town_index), 1, 205); if (theme === "multiplex") fill = String(p.multiplex_tag || "").includes("日间闲置") ? "#f0ad4e" : "#6e8b68"; if (["poi", "supply", "price", "rating"].includes(theme)) { const field = {poi:"poi_count", supply:"poi_count", price:"poi_avg_price", rating:"poi_score"}[theme]; const max = {poi:18, supply:18, price:253, rating:5}[theme]; fill = ramp(numeric(p[field]), max, 25); } if (theme === "mismatch") { const tag = p.mismatch_tag || p.mismatch_type || ""; fill = tag.includes("高人流") ? "#d9534f" : tag.includes("双热") ? "#f0ad4e" : "#6f8e6c"; } return {fillColor:fill, color, weight, fillOpacity}; } function statusText(status) { return status === "done" ? "已完成" : status === "base" ? "已有基础" : "正在开发"; } function rowsFor(category, item) { const data = window.BUILDING_MODEL_DATA || {}; const buildingCount = Array.isArray(data.buildings) ? data.buildings.length : 0; const streamCount = Array.isArray(data.runoff_streams) ? data.runoff_streams.length : 0; const facilityCount = Array.isArray(data.sponge_facilities) ? data.sponge_facilities.length : 0; const common = { water: [["数据能力", "地形 + 淹没 + 径流"], ["当前对象", `${buildingCount}栋 / ${streamCount}条路径`], ["当前图层", item[1]]], green: [["数据能力", `${facilityCount}处海绵设施方案`], ["项目数据", data.target_dataset || "当前项目"], ["当前图层", item[1]]], people: [["时间粒度", "每2小时"], ["日期类型", "工作日 / 周末"], ["当前图层", item[1]]], industry: [["分析单元", "建筑对象"], ["数据来源", "热力 + 商业POI"], ["当前图层", item[1]]], risk: [["分析对象", `${buildingCount}栋建筑`], ["现有字段", "结构 / 层数 / 风险"], ["当前图层", item[1]]] }; return common[category]; } function countBy(items, getter, preferred = []) { const counts = new Map(); items.forEach(entry => { const key = String(getter(entry) || "未分类"); counts.set(key, (counts.get(key) || 0) + 1); }); const keys = preferred.length ? preferred.filter(key => counts.has(key)) : [...counts.keys()].sort((a,b) => counts.get(b) - counts.get(a)).slice(0, 7); return {labels:keys, values:keys.map(key => counts.get(key) || 0)}; } function chartSeries(item) { const data = window.BUILDING_MODEL_DATA || {}; const buildings = Array.isArray(data.buildings) ? data.buildings : []; const facilities = Array.isArray(data.sponge_facilities) ? data.sponge_facilities : []; const theme = item[3]; if (theme === "sponge_risk") return countBy(buildings, b => b.sponge_risk_level, ["高倒灌风险","中度内涝风险","安全排水区"]); if (theme === "sponge_plan") return countBy(facilities, f => f.type || f.name); if (theme === "inundation_sim") { const bins = [0,0,0,0]; buildings.forEach(b => { const v=numeric(b.inundation_depth_m); bins[v<=0?0:v<=.15?1:v<=.3?2:3]++; }); return {labels:["未淹没","≤0.15m","0.15–0.30m",">0.30m"],values:bins}; } if (theme === "runoff_streams") return {labels:["汇流路径","海绵设施","高倒灌建筑"],values:[(data.runoff_streams||[]).length,facilities.length,buildings.filter(b=>b.sponge_risk_level==="高倒灌风险").length]}; if (theme === "planning") return countBy(buildings, b => b.structure); if (theme === "temporal_heat") { const labels=["00","02","04","06","08","10","12","14","16","18","20","22"]; const values=labels.map((_,i)=>{const vals=buildings.map(b=>numeric((b.heat_wd||[])[i],NaN)).filter(Number.isFinite);return vals.length?vals.reduce((a,v)=>a+v,0)/vals.length:0;}); return {labels,values}; } if (theme === "overclocking") return countBy(buildings, b => b.mismatch_tag || b.mismatch_type); if (theme === "floors") return countBy(buildings, b => `${numeric(b.layers || b.floors)}层`, ["1层","2层","3层","4层","5层","6层","7层","8层"]); return countBy(buildings, b => b.risk_level, ["高风险","中风险","低风险"]); } function drawAnalysisChart(item) { const canvas = document.getElementById("analysisChart"); if (!canvas) return; const box = canvas.getBoundingClientRect(); const dpr = Math.min(2, window.devicePixelRatio || 1); canvas.width = Math.max(1, Math.round(box.width*dpr)); canvas.height = Math.max(1, Math.round(box.height*dpr)); const ctx = canvas.getContext("2d"); ctx.scale(dpr,dpr); ctx.clearRect(0,0,box.width,box.height); const {labels,values}=chartSeries(item); const max=Math.max(1,...values); const left=28,right=5,top=10,bottom=39; const width=box.width-left-right; const height=box.height-top-bottom; const gap=6; const barW=Math.max(8,(width-gap*(values.length-1))/Math.max(1,values.length)); ctx.strokeStyle="rgba(17,17,17,.10)";ctx.lineWidth=1;ctx.beginPath();ctx.moveTo(left,top+height+.5);ctx.lineTo(left+width,top+height+.5);ctx.stroke(); values.forEach((value,index)=>{const h=Math.max(2,value/max*height);const x=left+index*(barW+gap);const y=top+height-h;ctx.fillStyle=index===0?"#315842":"#9fb4a4";ctx.beginPath();ctx.roundRect(x,y,barW,h,Math.min(5,barW/3));ctx.fill();ctx.fillStyle="#5f655f";ctx.font="9px Microsoft YaHei";ctx.textAlign="center";ctx.fillText(String(Math.round(value)),x+barW/2,Math.max(9,y-4));ctx.save();ctx.translate(x+barW/2,top+height+10);ctx.rotate(-.42);ctx.textAlign="right";ctx.fillText(String(labels[index]).slice(0,9),0,0);ctx.restore();}); document.getElementById("analysisChartCaption").textContent = `${item[1]} · 当前项目`; } function renderRight() { const category = CATEGORIES[dimension]; const item = currentItem(); const data = window.BUILDING_MODEL_DATA || {}; const macro = data.macro || {}; const buildings = Array.isArray(data.buildings) ? data.buildings : []; const streams = Array.isArray(data.runoff_streams) ? data.runoff_streams : []; const fixedMetrics = [ ["建筑总栋数", `${numeric(macro.total_buildings, buildings.length)}栋`], ["总建筑面积", `${(numeric(macro.total_gross_area_m2) / 10000).toFixed(2)}万㎡`], ["DEM地势落差", `${numeric(macro.elevation_delta_m).toFixed(1)}m`], ["汇流水系脉络", `${numeric(macro.streams_count, streams.length)}条`], ["高倒灌风险", `${numeric(macro.sponge_high_risk_buildings)}栋`], ["安全排水区", `${numeric(macro.sponge_safe_buildings)}栋`], ["海绵调蓄需求", `${numeric(macro.sponge_water_demand_m3).toLocaleString(undefined,{maximumFractionDigits:1})}m³`], ["建筑密度 / FAR", `${numeric(macro.building_density_pct).toFixed(2)}% / ${numeric(macro.far).toFixed(2)}`] ]; document.getElementById("analysisTitle").textContent = "项目总体数据"; document.getElementById("analysisState").textContent = `${category.title} · ${category.subtitle}`; document.getElementById("analysisScore").textContent = category.label; document.getElementById("analysisItemLabel").textContent = data.target_dataset || "测试地形图260821002"; document.getElementById("analysisSummary").textContent = `当前查看:${item[1]}。总体数据保持固定,专题数据与地图图层同步切换。`; document.getElementById("analysisKpis").innerHTML = fixedMetrics.map(([label, value]) => `
${label}${value}
`).join(""); document.getElementById("analysisSubitemTitle").textContent = item[1]; document.getElementById("analysisData").innerHTML = `

${item[1]} ${statusText(item[2])}

${rowsFor(dimension, item).map(([label,value]) => `
${label}${value}
`).join("")}`; const detail = document.getElementById("detail"); if (!detail.dataset.buildingId) detail.innerHTML = `

建筑体块数据

点击中间三维模型中的建筑,查看“${item[1]}”对应的完整数据。

`; requestAnimationFrame(() => drawAnalysisChart(item)); } function renderMapLegend(item) { const legend = LEGENDS[item[3]] || []; const title = document.getElementById("mapLegendTitle"); const items = document.getElementById("mapLegendItems"); const panel = document.getElementById("mapLegend"); if (!title || !items || !panel) return; title.textContent = `${item[1]}图例`; items.innerHTML = legend.map(([color,label,shape]) => `
${label}
`).join(""); panel.hidden = legend.length === 0; } function selectItem(key) { itemKey = key; const item = currentItem(); document.querySelectorAll("#analysisSubnav .layer").forEach(button => button.classList.toggle("active", button.dataset.analysis === key)); document.getElementById("layerName").textContent = item[1]; renderRight(); renderMapLegend(item); window.renderMap?.(); const targetTheme = theme3d[item[3]] || "planning"; window.sendThreeCommand?.("theme", {value:targetTheme}); } function renderSubnav() { const nav = document.getElementById("analysisSubnav"); const items = CATEGORIES[dimension].items; nav.innerHTML = items.map((item, index) => ``).join(""); nav.onclick = event => { const button = event.target.closest("button[data-analysis]:not(:disabled)"); if (button && nav.contains(button)) selectItem(button.dataset.analysis); }; } function selectDimension(next) { dimension = next; itemKey = CATEGORIES[next].items.find(item => item[2] === "done")?.[0] || CATEGORIES[next].items[0][0]; document.querySelectorAll("#twoDimensionGrid [data-dimension]").forEach(button => button.classList.toggle("active", button.dataset.dimension === next)); renderSubnav(); selectItem(itemKey); } function propertyText(p) { const item = currentItem(); const theme = theme2d[item[3]] || item[3]; if (theme === "roof") return `屋面面积 ${Math.round(numeric(p.footprint_area))}㎡,50mm降雨下估算屋面径流 ${(numeric(p.footprint_area)*0.05*0.85).toFixed(2)}m³。`; if (theme === "heat" || theme === "daynight") return `建筑热力均值 ${heatValue(p).toFixed(1)},包含工作日与周末各12个时段。`; if (["poi","supply","price","rating","mismatch","multiplex"].includes(theme)) return `POI ${numeric(p.poi_count)}个,供需类型:${p.mismatch_tag || p.mismatch_type || "暂无分类"},复用判断:${p.multiplex_tag || "暂无"}。`; if (theme === "fire" || theme === "spacing") return `实际间距 ${numeric(p.actual_spacing).toFixed(2)}m,${p.fire_spacing_issue ? "存在消防间距隐患" : "消防间距相对正常"}。`; if (theme === "temporary") return `${p.is_temporary ? "临时建筑,可作为减法更新和空间释放候选。" : "非临时建筑,当前专题下作为背景对象。"}`; if (theme === "inundation" || theme === "sponge" || theme === "elevation") return `建筑高程 ${numeric(p.elevation_m).toFixed(2)}m,模拟淹没深度 ${numeric(p.inundation_depth_m).toFixed(2)}m,海绵风险:${p.sponge_risk_level || "暂无"}。${p.sponge_recommendation || ""}`; return `风险等级 ${p.risk_level || "暂无"},结构类型:${p.structure || "暂无"}。`; } function showBuildingDetail(p) { const item = currentItem(); const id = p.building_id || p.id || "建筑对象"; const area = numeric(p.total_area || p.building_area); const heatWd = Array.isArray(p.heat_wd) ? p.heat_wd.map(numeric) : []; const heatWe = Array.isArray(p.heat_we) ? p.heat_we.map(numeric) : []; const heatBars = values => { const max = Math.max(1, ...values); return values.map(value => ``).join(""); }; const food = Math.max(0, numeric(p.food_ratio)); const entertainment = Math.max(0, numeric(p.ent_ratio)); const service = Math.max(0, numeric(p.service_ratio, 100 - food - entertainment)); const ratioTotal = Math.max(1, food + entertainment + service); const ratioWidth = value => `${value / ratioTotal * 100}%`; const detail = document.getElementById("detail"); detail.dataset.buildingId = id; detail.classList.remove("empty"); document.getElementById("analysisSubitemTitle").textContent = id; detail.innerHTML = `

${id}

CAD标注 · ${p.raw_text || "-"}
${p.is_temporary ? "临时违章建筑" : "永久性建筑"}${p.sponge_risk_level || "海绵风险待判定"}${p.risk_level || "结构风险待判定"}
层数 / 高度${numeric(p.layers || p.floors)}层 / ${numeric(p.height).toFixed(1)}m
DEM基底高程${numeric(p.elevation_m).toFixed(2)}m
结构体系${p.structure || "暂无"}
占地 / 总建面${numeric(p.footprint_area).toLocaleString()}㎡ / ${area.toLocaleString()}㎡
周边商户 POI${numeric(p.poi_count)}个 · ¥${numeric(p.poi_avg_price).toFixed(1)}
消费评分 / 错配${numeric(p.poi_score).toFixed(1)} · ${p.mismatch_tag || "暂无"}
受淹内涝深度${numeric(p.inundation_depth_m).toFixed(2)}m
当前专题${item[1]}
海绵城市体检与工程治理指引
${p.sponge_recommendation || propertyText(p)}
24小时人群流动 · 工作日
${heatBars(heatWd)}
24小时人群流动 · 周末
${heatBars(heatWe)}
消费业态比例
餐饮 ${food}%娱乐 ${entertainment}%服务 ${service}%
`; } window.AnalysisHierarchy = {selectDimension, selectItem, styleFeature, showBuildingDetail}; enrichData(); selectDimension("risk"); document.getElementById("threeView")?.addEventListener("load", () => { const item = currentItem(); window.sendThreeCommand?.("theme", {value:theme3d[item[3]] || "planning"}); }); const buildingModal = document.getElementById("buildingModal"); const closeBuildingModal = () => { buildingModal?.classList.remove("open"); buildingModal?.setAttribute("aria-hidden","true"); }; document.getElementById("buildingModalClose")?.addEventListener("click", closeBuildingModal); buildingModal?.addEventListener("click", event => { if (event.target === buildingModal) closeBuildingModal(); }); document.addEventListener("keydown", event => { if (event.key === "Escape") closeBuildingModal(); }); window.addEventListener("resize", () => requestAnimationFrame(() => drawAnalysisChart(currentItem()))); })();