[{"data":1,"prerenderedAt":6546},["ShallowReactive",2],{"blog-page":3,"blogs":15},{"id":4,"title":5,"body":6,"description":7,"extension":8,"links":6,"meta":9,"navigation":10,"path":11,"seo":12,"stem":13,"__hash__":14},"pages\u002Fblog.yml","技术文章",null,"记录 WebGIS、Cesium 三维可视化、前端工程化和个人技术 Demo 的实践笔记。","yml",{},true,"\u002Fblog",{"title":5,"description":7},"blog","H-37Ky9YGUQo69Rm-OMduJ3QL7JaTRRzk6Dx8ERHkqI",[16,1643],{"id":17,"title":18,"author":19,"body":23,"date":1634,"description":1635,"extension":1636,"image":1637,"meta":1638,"minRead":1357,"navigation":10,"path":1639,"seo":1640,"stem":1641,"__hash__":1642},"blog\u002Fblog\u002Fcesium-terrain-heightmap-implementation.md","Cesium 最高地形瓦片高度图实现说明",{"name":20,"avatar":21},"Kevin",{"src":22,"alt":20},"\u002Favatar.jpg",{"type":24,"value":25,"toc":1623},"minimark",[26,30,34,69,72,75,78,215,218,241,244,247,254,365,372,541,544,547,550,658,665,771,781,785,794,890,897,900,911,1039,1042,1078,1089,1113,1116,1119,1233,1387,1390,1395,1423,1426,1452,1459,1462,1471,1520,1527,1530,1533,1608,1619],[27,28,29],"h2",{"id":29},"这篇笔记解决什么问题",[31,32,33],"p",{},"这个 Demo 的目标不是把一次性逻辑堆到页面里，而是把“从地形生成高度图”拆成可以复用的 SDK 模块：",[35,36,37,45,51,57,63],"ul",{},[38,39,40,44],"li",{},[41,42,43],"code",{},"rectangleDraw.js"," 只负责地形拾取和矩形绘制",[38,46,47,50],{},[41,48,49],{},"terrainSampler.js"," 只负责最高层级解析和规则网格采样",[38,52,53,56],{},[41,54,55],{},"heightmapCanvas.js"," 只负责把高度数组转成灰度 canvas",[38,58,59,62],{},[41,60,61],{},"heightmapSurface.js"," 只负责创建带真实高度顶点的 Cesium Primitive",[38,64,65,68],{},[41,66,67],{},"CesiumTerrainHeightmapDemo.js"," 负责把这些能力组装成 Demo runtime",[31,70,71],{},"这样后续如果要把高度图接入水流模拟、侵蚀模拟或碰撞计算，可以复用采样和 canvas 输出模块，而不需要复制整页交互。",[27,73,74],{"id":74},"整体调用链",[31,76,77],{},"页面启动后先通过统一 runtime 加载 Cesium，再创建 Viewer 和 World Terrain。GUI 暴露三个入口：绘制提取范围、切换地形深度测试、加载或取消地形。",[79,80,85],"pre",{"className":81,"code":82,"language":83,"meta":84,"style":84},"language-js shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","gui.add(controls, 'drawRange').name('绘制提取范围')\ngui.add(controls, 'terrainDepthTest').name('地形深度测试')\ngui.add(controls, 'toggleTerrain').name('加载 \u002F 取消地形')\n","js","",[41,86,87,141,178],{"__ignoreMap":84},[88,89,92,96,100,104,107,110,113,117,120,123,125,128,131,133,136,138],"span",{"class":90,"line":91},"line",1,[88,93,95],{"class":94},"sTEyZ","gui",[88,97,99],{"class":98},"sMK4o",".",[88,101,103],{"class":102},"s2Zo4","add",[88,105,106],{"class":94},"(controls",[88,108,109],{"class":98},",",[88,111,112],{"class":98}," '",[88,114,116],{"class":115},"sfazB","drawRange",[88,118,119],{"class":98},"'",[88,121,122],{"class":94},")",[88,124,99],{"class":98},[88,126,127],{"class":102},"name",[88,129,130],{"class":94},"(",[88,132,119],{"class":98},[88,134,135],{"class":115},"绘制提取范围",[88,137,119],{"class":98},[88,139,140],{"class":94},")\n",[88,142,144,146,148,150,152,154,156,159,161,163,165,167,169,171,174,176],{"class":90,"line":143},2,[88,145,95],{"class":94},[88,147,99],{"class":98},[88,149,103],{"class":102},[88,151,106],{"class":94},[88,153,109],{"class":98},[88,155,112],{"class":98},[88,157,158],{"class":115},"terrainDepthTest",[88,160,119],{"class":98},[88,162,122],{"class":94},[88,164,99],{"class":98},[88,166,127],{"class":102},[88,168,130],{"class":94},[88,170,119],{"class":98},[88,172,173],{"class":115},"地形深度测试",[88,175,119],{"class":98},[88,177,140],{"class":94},[88,179,181,183,185,187,189,191,193,196,198,200,202,204,206,208,211,213],{"class":90,"line":180},3,[88,182,95],{"class":94},[88,184,99],{"class":98},[88,186,103],{"class":102},[88,188,106],{"class":94},[88,190,109],{"class":98},[88,192,112],{"class":98},[88,194,195],{"class":115},"toggleTerrain",[88,197,119],{"class":98},[88,199,122],{"class":94},[88,201,99],{"class":98},[88,203,127],{"class":102},[88,205,130],{"class":94},[88,207,119],{"class":98},[88,209,210],{"class":115},"加载 \u002F 取消地形",[88,212,119],{"class":98},[88,214,140],{"class":94},[31,216,217],{},"用户完成矩形绘制后，runtime 会顺序执行：",[219,220,221,226,231,236],"ol",{},[38,222,223],{},[41,224,225],{},"sampleTerrainHeightmap(viewer, { rectangle })",[38,227,228],{},[41,229,230],{},"renderTerrainHeightmapCanvas(sampleResult)",[38,232,233],{},[41,234,235],{},"createTerrainHeightmapSurface(viewer, sampleResult, heightmap.canvas)",[38,237,238],{},[41,239,240],{},"heightmapPanel.update(sampleResult, heightmap)",[31,242,243],{},"这条链路保证左下角预览图和场景中的起伏面使用同一份高度结果。",[27,245,246],{"id":246},"矩形绘制怎么做",[31,248,249,250,253],{},"绘制模块使用 Cesium 的屏幕事件系统，第一次左键记录起点，鼠标移动时更新预览矩形，第二次左键完成范围。拾取点优先走 ",[41,251,252],{},"globe.pick","，这样鼠标落在真实地形上时能得到地形表面位置。",[79,255,257],{"className":81,"code":256,"language":83,"meta":84,"style":84},"const ray = viewer.camera.getPickRay(windowPosition)\n\nreturn viewer.scene.globe.pick(ray, viewer.scene) ||\n  viewer.camera.pickEllipsoid(windowPosition, viewer.scene.globe.ellipsoid)\n",[41,258,259,287,292,330],{"__ignoreMap":84},[88,260,261,265,268,271,274,276,279,281,284],{"class":90,"line":91},[88,262,264],{"class":263},"spNyl","const",[88,266,267],{"class":94}," ray ",[88,269,270],{"class":98},"=",[88,272,273],{"class":94}," viewer",[88,275,99],{"class":98},[88,277,278],{"class":94},"camera",[88,280,99],{"class":98},[88,282,283],{"class":102},"getPickRay",[88,285,286],{"class":94},"(windowPosition)\n",[88,288,289],{"class":90,"line":143},[88,290,291],{"emptyLinePlaceholder":10},"\n",[88,293,294,298,300,302,305,307,310,312,315,318,320,322,324,327],{"class":90,"line":180},[88,295,297],{"class":296},"s7zQu","return",[88,299,273],{"class":94},[88,301,99],{"class":98},[88,303,304],{"class":94},"scene",[88,306,99],{"class":98},[88,308,309],{"class":94},"globe",[88,311,99],{"class":98},[88,313,314],{"class":102},"pick",[88,316,317],{"class":94},"(ray",[88,319,109],{"class":98},[88,321,273],{"class":94},[88,323,99],{"class":98},[88,325,326],{"class":94},"scene) ",[88,328,329],{"class":98},"||\n",[88,331,333,336,338,340,342,345,348,350,352,354,356,358,360,362],{"class":90,"line":332},4,[88,334,335],{"class":94},"  viewer",[88,337,99],{"class":98},[88,339,278],{"class":94},[88,341,99],{"class":98},[88,343,344],{"class":102},"pickEllipsoid",[88,346,347],{"class":94},"(windowPosition",[88,349,109],{"class":98},[88,351,273],{"class":94},[88,353,99],{"class":98},[88,355,304],{"class":94},[88,357,99],{"class":98},[88,359,309],{"class":94},[88,361,99],{"class":98},[88,363,364],{"class":94},"ellipsoid)\n",[31,366,367,368,371],{},"完成时会把两个角点归一化为 ",[41,369,370],{},"Cesium.Rectangle","：",[79,373,375],{"className":81,"code":374,"language":83,"meta":84,"style":84},"const west = Math.min(startCartographic.longitude, endCartographic.longitude)\nconst east = Math.max(startCartographic.longitude, endCartographic.longitude)\nconst south = Math.min(startCartographic.latitude, endCartographic.latitude)\nconst north = Math.max(startCartographic.latitude, endCartographic.latitude)\n\nreturn new Cesium.Rectangle(west, south, east, north)\n",[41,376,377,412,442,473,502,507],{"__ignoreMap":84},[88,378,379,381,384,386,389,391,394,397,399,402,404,407,409],{"class":90,"line":91},[88,380,264],{"class":263},[88,382,383],{"class":94}," west ",[88,385,270],{"class":98},[88,387,388],{"class":94}," Math",[88,390,99],{"class":98},[88,392,393],{"class":102},"min",[88,395,396],{"class":94},"(startCartographic",[88,398,99],{"class":98},[88,400,401],{"class":94},"longitude",[88,403,109],{"class":98},[88,405,406],{"class":94}," endCartographic",[88,408,99],{"class":98},[88,410,411],{"class":94},"longitude)\n",[88,413,414,416,419,421,423,425,428,430,432,434,436,438,440],{"class":90,"line":143},[88,415,264],{"class":263},[88,417,418],{"class":94}," east ",[88,420,270],{"class":98},[88,422,388],{"class":94},[88,424,99],{"class":98},[88,426,427],{"class":102},"max",[88,429,396],{"class":94},[88,431,99],{"class":98},[88,433,401],{"class":94},[88,435,109],{"class":98},[88,437,406],{"class":94},[88,439,99],{"class":98},[88,441,411],{"class":94},[88,443,444,446,449,451,453,455,457,459,461,464,466,468,470],{"class":90,"line":180},[88,445,264],{"class":263},[88,447,448],{"class":94}," south ",[88,450,270],{"class":98},[88,452,388],{"class":94},[88,454,99],{"class":98},[88,456,393],{"class":102},[88,458,396],{"class":94},[88,460,99],{"class":98},[88,462,463],{"class":94},"latitude",[88,465,109],{"class":98},[88,467,406],{"class":94},[88,469,99],{"class":98},[88,471,472],{"class":94},"latitude)\n",[88,474,475,477,480,482,484,486,488,490,492,494,496,498,500],{"class":90,"line":332},[88,476,264],{"class":263},[88,478,479],{"class":94}," north ",[88,481,270],{"class":98},[88,483,388],{"class":94},[88,485,99],{"class":98},[88,487,427],{"class":102},[88,489,396],{"class":94},[88,491,99],{"class":98},[88,493,463],{"class":94},[88,495,109],{"class":98},[88,497,406],{"class":94},[88,499,99],{"class":98},[88,501,472],{"class":94},[88,503,505],{"class":90,"line":504},5,[88,506,291],{"emptyLinePlaceholder":10},[88,508,510,512,515,518,520,523,526,528,531,533,536,538],{"class":90,"line":509},6,[88,511,297],{"class":296},[88,513,514],{"class":98}," new",[88,516,517],{"class":94}," Cesium",[88,519,99],{"class":98},[88,521,522],{"class":102},"Rectangle",[88,524,525],{"class":94},"(west",[88,527,109],{"class":98},[88,529,530],{"class":94}," south",[88,532,109],{"class":98},[88,534,535],{"class":94}," east",[88,537,109],{"class":98},[88,539,540],{"class":94}," north)\n",[31,542,543],{},"当前版本不处理跨国际日期变更线的特殊矩形，适合常规局部地形提取。",[27,545,546],{"id":546},"最高层级采样怎么做",[31,548,549],{},"参考文章里的核心思想是“先找矩形范围内最高可用地形瓦片，再把高度写进高度图”。这里为了保持 Demo 稳定，使用 Cesium 公开 API 获取范围内整体可用最高层级：",[79,551,553],{"className":81,"code":552,"language":83,"meta":84,"style":84},"const availability = terrainProvider?.availability\nconst level = availability?.computeBestAvailableLevelOverRectangle?.(rectangle)\n\nif (Number.isFinite(level)) {\n  return Math.max(0, Math.floor(level))\n}\n",[41,554,555,573,595,599,618,653],{"__ignoreMap":84},[88,556,557,559,562,564,567,570],{"class":90,"line":91},[88,558,264],{"class":263},[88,560,561],{"class":94}," availability ",[88,563,270],{"class":98},[88,565,566],{"class":94}," terrainProvider",[88,568,569],{"class":98},"?.",[88,571,572],{"class":94},"availability\n",[88,574,575,577,580,582,585,587,590,592],{"class":90,"line":143},[88,576,264],{"class":263},[88,578,579],{"class":94}," level ",[88,581,270],{"class":98},[88,583,584],{"class":94}," availability",[88,586,569],{"class":98},[88,588,589],{"class":102},"computeBestAvailableLevelOverRectangle",[88,591,569],{"class":98},[88,593,594],{"class":94},"(rectangle)\n",[88,596,597],{"class":90,"line":180},[88,598,291],{"emptyLinePlaceholder":10},[88,600,601,604,607,609,612,615],{"class":90,"line":332},[88,602,603],{"class":296},"if",[88,605,606],{"class":94}," (Number",[88,608,99],{"class":98},[88,610,611],{"class":102},"isFinite",[88,613,614],{"class":94},"(level)) ",[88,616,617],{"class":98},"{\n",[88,619,620,623,625,627,629,632,636,638,640,642,645,647,650],{"class":90,"line":504},[88,621,622],{"class":296},"  return",[88,624,388],{"class":94},[88,626,99],{"class":98},[88,628,427],{"class":102},[88,630,130],{"class":631},"swJcz",[88,633,635],{"class":634},"sbssI","0",[88,637,109],{"class":98},[88,639,388],{"class":94},[88,641,99],{"class":98},[88,643,644],{"class":102},"floor",[88,646,130],{"class":631},[88,648,649],{"class":94},"level",[88,651,652],{"class":631},"))\n",[88,654,655],{"class":90,"line":509},[88,656,657],{"class":98},"}\n",[31,659,660,661,664],{},"拿到层级后，模块按矩形宽高比生成规则采样网格，最大边默认限制为 ",[41,662,663],{},"96"," 个采样点，防止一次请求过大范围时拖垮页面。",[79,666,668],{"className":81,"code":667,"language":83,"meta":84,"style":84},"const { columns, rows } = resolveGridSize(Cesium, rectangle, maxGridSide)\nconst positions = createGridPositions(Cesium, rectangle, columns, rows)\n\nawait Cesium.sampleTerrain(terrainProvider, level, positions, false)\n",[41,669,670,707,734,738],{"__ignoreMap":84},[88,671,672,674,677,680,682,685,688,691,694,697,699,702,704],{"class":90,"line":91},[88,673,264],{"class":263},[88,675,676],{"class":98}," {",[88,678,679],{"class":94}," columns",[88,681,109],{"class":98},[88,683,684],{"class":94}," rows ",[88,686,687],{"class":98},"}",[88,689,690],{"class":98}," =",[88,692,693],{"class":102}," resolveGridSize",[88,695,696],{"class":94},"(Cesium",[88,698,109],{"class":98},[88,700,701],{"class":94}," rectangle",[88,703,109],{"class":98},[88,705,706],{"class":94}," maxGridSide)\n",[88,708,709,711,714,716,719,721,723,725,727,729,731],{"class":90,"line":143},[88,710,264],{"class":263},[88,712,713],{"class":94}," positions ",[88,715,270],{"class":98},[88,717,718],{"class":102}," createGridPositions",[88,720,696],{"class":94},[88,722,109],{"class":98},[88,724,701],{"class":94},[88,726,109],{"class":98},[88,728,679],{"class":94},[88,730,109],{"class":98},[88,732,733],{"class":94}," rows)\n",[88,735,736],{"class":90,"line":180},[88,737,291],{"emptyLinePlaceholder":10},[88,739,740,743,745,747,750,753,755,758,760,763,765,769],{"class":90,"line":332},[88,741,742],{"class":296},"await",[88,744,517],{"class":94},[88,746,99],{"class":98},[88,748,749],{"class":102},"sampleTerrain",[88,751,752],{"class":94},"(terrainProvider",[88,754,109],{"class":98},[88,756,757],{"class":94}," level",[88,759,109],{"class":98},[88,761,762],{"class":94}," positions",[88,764,109],{"class":98},[88,766,768],{"class":767},"sfNiH"," false",[88,770,140],{"class":94},[31,772,773,776,777,780],{},[41,774,775],{},"sampleTerrain()"," 会按瓦片组织请求，并调用对应 ",[41,778,779],{},"TerrainData.interpolateHeight()"," 得到每个经纬度点的地形高度。局部缺失的高度会用邻域高度补齐，保证后续 canvas 和网格不会出现空洞。",[27,782,784],{"id":783},"高度图-canvas-怎么生成","高度图 canvas 怎么生成",[31,786,787,789,790,793],{},[41,788,55],{}," 会根据矩形投影宽高比创建 canvas，最大边默认 ",[41,791,792],{},"512px","。每个像素通过双线性插值从采样网格中读取高度，再按当前范围内的最小值和最大值归一化成灰度。",[79,795,797],{"className":81,"code":796,"language":83,"meta":84,"style":84},"const height = sampleHeightBilinear(sampleResult, u, v)\nconst intensity = normalizeHeight(height, sampleResult.minHeight, sampleResult.heightRange)\nconst value = clampInteger(intensity * 255, 0, 255)\n",[41,798,799,824,858],{"__ignoreMap":84},[88,800,801,803,806,808,811,814,816,819,821],{"class":90,"line":91},[88,802,264],{"class":263},[88,804,805],{"class":94}," height ",[88,807,270],{"class":98},[88,809,810],{"class":102}," sampleHeightBilinear",[88,812,813],{"class":94},"(sampleResult",[88,815,109],{"class":98},[88,817,818],{"class":94}," u",[88,820,109],{"class":98},[88,822,823],{"class":94}," v)\n",[88,825,826,828,831,833,836,839,841,844,846,849,851,853,855],{"class":90,"line":143},[88,827,264],{"class":263},[88,829,830],{"class":94}," intensity ",[88,832,270],{"class":98},[88,834,835],{"class":102}," normalizeHeight",[88,837,838],{"class":94},"(height",[88,840,109],{"class":98},[88,842,843],{"class":94}," sampleResult",[88,845,99],{"class":98},[88,847,848],{"class":94},"minHeight",[88,850,109],{"class":98},[88,852,843],{"class":94},[88,854,99],{"class":98},[88,856,857],{"class":94},"heightRange)\n",[88,859,860,862,865,867,870,873,876,879,881,884,886,888],{"class":90,"line":180},[88,861,264],{"class":263},[88,863,864],{"class":94}," value ",[88,866,270],{"class":98},[88,868,869],{"class":102}," clampInteger",[88,871,872],{"class":94},"(intensity ",[88,874,875],{"class":98},"*",[88,877,878],{"class":634}," 255",[88,880,109],{"class":98},[88,882,883],{"class":634}," 0",[88,885,109],{"class":98},[88,887,878],{"class":634},[88,889,140],{"class":94},[31,891,892,893,896],{},"这里输出的是当前范围内的相对灰度高度图：黑色代表当前范围内较低区域，白色代表较高区域。左下角面板直接挂载这张 canvas，场景中的起伏面也把同一张 canvas 当作 ",[41,894,895],{},"Image"," 材质使用。",[27,898,899],{"id":899},"起伏面怎么创建",[31,901,902,903,906,907,910],{},"场景面不是贴地影像，也不是 ",[41,904,905],{},"GroundPrimitive","。它是一个真实三角网格：每个采样点都会转换成带高度的 ",[41,908,909],{},"Cartesian3","，并按规则网格生成三角索引。",[79,912,914],{"className":81,"code":913,"language":83,"meta":84,"style":84},"const height = sampleResult.heights[index] + heightOffset\nconst cartesian = Cesium.Cartesian3.fromRadians(cartographic.longitude, cartographic.latitude, height)\n\npositions[positionOffset] = cartesian.x\npositions[positionOffset + 1] = cartesian.y\npositions[positionOffset + 2] = cartesian.z\n",[41,915,916,937,978,982,997,1019],{"__ignoreMap":84},[88,917,918,920,922,924,926,928,931,934],{"class":90,"line":91},[88,919,264],{"class":263},[88,921,805],{"class":94},[88,923,270],{"class":98},[88,925,843],{"class":94},[88,927,99],{"class":98},[88,929,930],{"class":94},"heights[index] ",[88,932,933],{"class":98},"+",[88,935,936],{"class":94}," heightOffset\n",[88,938,939,941,944,946,948,950,952,954,957,960,962,964,966,969,971,973,975],{"class":90,"line":143},[88,940,264],{"class":263},[88,942,943],{"class":94}," cartesian ",[88,945,270],{"class":98},[88,947,517],{"class":94},[88,949,99],{"class":98},[88,951,909],{"class":94},[88,953,99],{"class":98},[88,955,956],{"class":102},"fromRadians",[88,958,959],{"class":94},"(cartographic",[88,961,99],{"class":98},[88,963,401],{"class":94},[88,965,109],{"class":98},[88,967,968],{"class":94}," cartographic",[88,970,99],{"class":98},[88,972,463],{"class":94},[88,974,109],{"class":98},[88,976,977],{"class":94}," height)\n",[88,979,980],{"class":90,"line":180},[88,981,291],{"emptyLinePlaceholder":10},[88,983,984,987,989,992,994],{"class":90,"line":332},[88,985,986],{"class":94},"positions[positionOffset] ",[88,988,270],{"class":98},[88,990,991],{"class":94}," cartesian",[88,993,99],{"class":98},[88,995,996],{"class":94},"x\n",[88,998,999,1002,1004,1007,1010,1012,1014,1016],{"class":90,"line":504},[88,1000,1001],{"class":94},"positions[positionOffset ",[88,1003,933],{"class":98},[88,1005,1006],{"class":634}," 1",[88,1008,1009],{"class":94},"] ",[88,1011,270],{"class":98},[88,1013,991],{"class":94},[88,1015,99],{"class":98},[88,1017,1018],{"class":94},"y\n",[88,1020,1021,1023,1025,1028,1030,1032,1034,1036],{"class":90,"line":509},[88,1022,1001],{"class":94},[88,1024,933],{"class":98},[88,1026,1027],{"class":634}," 2",[88,1029,1009],{"class":94},[88,1031,270],{"class":98},[88,1033,991],{"class":94},[88,1035,99],{"class":98},[88,1037,1038],{"class":94},"z\n",[31,1040,1041],{},"纹理坐标按西到东、北到南映射，保证 canvas 高度图和矩形范围方向一致。",[79,1043,1045],{"className":81,"code":1044,"language":83,"meta":84,"style":84},"textureCoordinates[stOffset] = columnRatio\ntextureCoordinates[stOffset + 1] = 1 - rowRatio\n",[41,1046,1047,1057],{"__ignoreMap":84},[88,1048,1049,1052,1054],{"class":90,"line":91},[88,1050,1051],{"class":94},"textureCoordinates[stOffset] ",[88,1053,270],{"class":98},[88,1055,1056],{"class":94}," columnRatio\n",[88,1058,1059,1062,1064,1066,1068,1070,1072,1075],{"class":90,"line":143},[88,1060,1061],{"class":94},"textureCoordinates[stOffset ",[88,1063,933],{"class":98},[88,1065,1006],{"class":634},[88,1067,1009],{"class":94},[88,1069,270],{"class":98},[88,1071,1006],{"class":634},[88,1073,1074],{"class":98}," -",[88,1076,1077],{"class":94}," rowRatio\n",[31,1079,1080,1081,1084,1085,1088],{},"最后用 ",[41,1082,1083],{},"GeometryPipeline.computeNormal()"," 计算法线，再交给 ",[41,1086,1087],{},"MaterialAppearance + Image material"," 渲染。",[79,1090,1092],{"className":81,"code":1091,"language":83,"meta":84,"style":84},"return Cesium.GeometryPipeline.computeNormal(geometry)\n",[41,1093,1094],{"__ignoreMap":84},[88,1095,1096,1098,1100,1102,1105,1107,1110],{"class":90,"line":91},[88,1097,297],{"class":296},[88,1099,517],{"class":94},[88,1101,99],{"class":98},[88,1103,1104],{"class":94},"GeometryPipeline",[88,1106,99],{"class":98},[88,1108,1109],{"class":102},"computeNormal",[88,1111,1112],{"class":94},"(geometry)\n",[31,1114,1115],{},"这个面已经拥有自己的地形高度顶点，所以即使后续隐藏或替换 Cesium 地形，它仍然保留原本采样到的凹凸起伏。",[31,1117,1118],{},"为了避免生成面像半透明覆盖层一样透视底下地形，canvas 输出使用满 alpha，材质也走不透明渲染并开启深度写入。",[79,1120,1122],{"className":81,"code":1121,"language":83,"meta":84,"style":84},"return Cesium.Material.fromType('Image', {\n  image: canvas,\n  repeat: new Cesium.Cartesian2(1, 1),\n  color: Cesium.Color.WHITE,\n  transparent: false\n})\n",[41,1123,1124,1153,1167,1196,1217,1227],{"__ignoreMap":84},[88,1125,1126,1128,1130,1132,1135,1137,1140,1142,1144,1146,1148,1150],{"class":90,"line":91},[88,1127,297],{"class":296},[88,1129,517],{"class":94},[88,1131,99],{"class":98},[88,1133,1134],{"class":94},"Material",[88,1136,99],{"class":98},[88,1138,1139],{"class":102},"fromType",[88,1141,130],{"class":94},[88,1143,119],{"class":98},[88,1145,895],{"class":115},[88,1147,119],{"class":98},[88,1149,109],{"class":98},[88,1151,1152],{"class":98}," {\n",[88,1154,1155,1158,1161,1164],{"class":90,"line":143},[88,1156,1157],{"class":631},"  image",[88,1159,1160],{"class":98},":",[88,1162,1163],{"class":94}," canvas",[88,1165,1166],{"class":98},",\n",[88,1168,1169,1172,1174,1176,1178,1180,1183,1185,1188,1190,1192,1194],{"class":90,"line":180},[88,1170,1171],{"class":631},"  repeat",[88,1173,1160],{"class":98},[88,1175,514],{"class":98},[88,1177,517],{"class":94},[88,1179,99],{"class":98},[88,1181,1182],{"class":102},"Cartesian2",[88,1184,130],{"class":94},[88,1186,1187],{"class":634},"1",[88,1189,109],{"class":98},[88,1191,1006],{"class":634},[88,1193,122],{"class":94},[88,1195,1166],{"class":98},[88,1197,1198,1201,1203,1205,1207,1210,1212,1215],{"class":90,"line":332},[88,1199,1200],{"class":631},"  color",[88,1202,1160],{"class":98},[88,1204,517],{"class":94},[88,1206,99],{"class":98},[88,1208,1209],{"class":94},"Color",[88,1211,99],{"class":98},[88,1213,1214],{"class":94},"WHITE",[88,1216,1166],{"class":98},[88,1218,1219,1222,1224],{"class":90,"line":504},[88,1220,1221],{"class":631},"  transparent",[88,1223,1160],{"class":98},[88,1225,1226],{"class":767}," false\n",[88,1228,1229,1231],{"class":90,"line":509},[88,1230,687],{"class":98},[88,1232,140],{"class":94},[79,1234,1236],{"className":81,"code":1235,"language":83,"meta":84,"style":84},"appearance: new Cesium.MaterialAppearance({\n  material,\n  materialSupport: Cesium.MaterialAppearance.MaterialSupport.TEXTURED,\n  translucent: false,\n  faceForward: true,\n  renderState: {\n    depthTest: { enabled: true },\n    depthMask: true,\n    cull: { enabled: false }\n  }\n})\n",[41,1237,1238,1259,1266,1291,1302,1314,1323,1343,1355,1374,1380],{"__ignoreMap":84},[88,1239,1240,1244,1246,1248,1250,1252,1255,1257],{"class":90,"line":91},[88,1241,1243],{"class":1242},"sBMFI","appearance",[88,1245,1160],{"class":98},[88,1247,514],{"class":98},[88,1249,517],{"class":94},[88,1251,99],{"class":98},[88,1253,1254],{"class":102},"MaterialAppearance",[88,1256,130],{"class":94},[88,1258,617],{"class":98},[88,1260,1261,1264],{"class":90,"line":143},[88,1262,1263],{"class":94},"  material",[88,1265,1166],{"class":98},[88,1267,1268,1271,1273,1275,1277,1279,1281,1284,1286,1289],{"class":90,"line":180},[88,1269,1270],{"class":631},"  materialSupport",[88,1272,1160],{"class":98},[88,1274,517],{"class":94},[88,1276,99],{"class":98},[88,1278,1254],{"class":94},[88,1280,99],{"class":98},[88,1282,1283],{"class":94},"MaterialSupport",[88,1285,99],{"class":98},[88,1287,1288],{"class":94},"TEXTURED",[88,1290,1166],{"class":98},[88,1292,1293,1296,1298,1300],{"class":90,"line":332},[88,1294,1295],{"class":631},"  translucent",[88,1297,1160],{"class":98},[88,1299,768],{"class":767},[88,1301,1166],{"class":98},[88,1303,1304,1307,1309,1312],{"class":90,"line":504},[88,1305,1306],{"class":631},"  faceForward",[88,1308,1160],{"class":98},[88,1310,1311],{"class":767}," true",[88,1313,1166],{"class":98},[88,1315,1316,1319,1321],{"class":90,"line":509},[88,1317,1318],{"class":631},"  renderState",[88,1320,1160],{"class":98},[88,1322,1152],{"class":98},[88,1324,1326,1329,1331,1333,1336,1338,1340],{"class":90,"line":1325},7,[88,1327,1328],{"class":631},"    depthTest",[88,1330,1160],{"class":98},[88,1332,676],{"class":98},[88,1334,1335],{"class":631}," enabled",[88,1337,1160],{"class":98},[88,1339,1311],{"class":767},[88,1341,1342],{"class":98}," },\n",[88,1344,1346,1349,1351,1353],{"class":90,"line":1345},8,[88,1347,1348],{"class":631},"    depthMask",[88,1350,1160],{"class":98},[88,1352,1311],{"class":767},[88,1354,1166],{"class":98},[88,1356,1358,1361,1363,1365,1367,1369,1371],{"class":90,"line":1357},9,[88,1359,1360],{"class":631},"    cull",[88,1362,1160],{"class":98},[88,1364,676],{"class":98},[88,1366,1335],{"class":631},[88,1368,1160],{"class":98},[88,1370,768],{"class":767},[88,1372,1373],{"class":98}," }\n",[88,1375,1377],{"class":90,"line":1376},10,[88,1378,1379],{"class":98},"  }\n",[88,1381,1383,1385],{"class":90,"line":1382},11,[88,1384,687],{"class":98},[88,1386,140],{"class":94},[27,1388,1389],{"id":1389},"地形深度测试的作用",[31,1391,1392,1394],{},[41,1393,173],{}," 按钮只切换一个 Cesium 场景状态：",[79,1396,1398],{"className":81,"code":1397,"language":83,"meta":84,"style":84},"viewer.scene.globe.depthTestAgainstTerrain = nextValue\n",[41,1399,1400],{"__ignoreMap":84},[88,1401,1402,1405,1407,1409,1411,1413,1415,1418,1420],{"class":90,"line":91},[88,1403,1404],{"class":94},"viewer",[88,1406,99],{"class":98},[88,1408,304],{"class":94},[88,1410,99],{"class":98},[88,1412,309],{"class":94},[88,1414,99],{"class":98},[88,1416,1417],{"class":94},"depthTestAgainstTerrain ",[88,1419,270],{"class":98},[88,1421,1422],{"class":94}," nextValue\n",[31,1424,1425],{},"这个 Demo 默认开启地形深度测试，让生成面一开始就和真实地形保持正确遮挡关系。",[79,1427,1429],{"className":81,"code":1428,"language":83,"meta":84,"style":84},"viewer.scene.globe.depthTestAgainstTerrain = true\n",[41,1430,1431],{"__ignoreMap":84},[88,1432,1433,1435,1437,1439,1441,1443,1445,1447,1449],{"class":90,"line":91},[88,1434,1404],{"class":94},[88,1436,99],{"class":98},[88,1438,304],{"class":94},[88,1440,99],{"class":98},[88,1442,309],{"class":94},[88,1444,99],{"class":98},[88,1446,1417],{"class":94},[88,1448,270],{"class":98},[88,1450,1451],{"class":767}," true\n",[31,1453,1454,1455,1458],{},"关闭时，高度图面更容易完整观察；开启时，真实地形会参与深度遮挡，可以用来判断生成面是否确实贴近当前地形。为了减少完全共面导致的闪烁，生成面只增加 ",[41,1456,1457],{},"0.35m"," 的显示偏移，高度变化仍来自真实采样结果。",[27,1460,1461],{"id":1461},"地形加载开关",[31,1463,1464,1466,1467,1470],{},[41,1465,210],{}," 按钮用于在 Cesium World Terrain 和椭球地形之间切换。取消加载时只替换 ",[41,1468,1469],{},"viewer.terrainProvider","，不会销毁已经生成的高度图 Primitive，所以可以验证这个面是否真的拥有自己的高度顶点。",[79,1472,1474],{"className":81,"code":1473,"language":83,"meta":84,"style":84},"viewer.terrainProvider = new Cesium.EllipsoidTerrainProvider()\nviewer.scene.globe.enableLighting = false\n",[41,1475,1476,1499],{"__ignoreMap":84},[88,1477,1478,1480,1482,1485,1487,1489,1491,1493,1496],{"class":90,"line":91},[88,1479,1404],{"class":94},[88,1481,99],{"class":98},[88,1483,1484],{"class":94},"terrainProvider ",[88,1486,270],{"class":98},[88,1488,514],{"class":98},[88,1490,517],{"class":94},[88,1492,99],{"class":98},[88,1494,1495],{"class":102},"EllipsoidTerrainProvider",[88,1497,1498],{"class":94},"()\n",[88,1500,1501,1503,1505,1507,1509,1511,1513,1516,1518],{"class":90,"line":143},[88,1502,1404],{"class":94},[88,1504,99],{"class":98},[88,1506,304],{"class":94},[88,1508,99],{"class":98},[88,1510,309],{"class":94},[88,1512,99],{"class":98},[88,1514,1515],{"class":94},"enableLighting ",[88,1517,270],{"class":98},[88,1519,1226],{"class":767},[31,1521,1522,1523,1526],{},"重新加载时会复用已经创建过的 World Terrain provider；如果还没有缓存，则再次调用 ",[41,1524,1525],{},"createWorldTerrainAsync()","。",[27,1528,1529],{"id":1529},"生命周期清理",[31,1531,1532],{},"Demo 销毁时会统一清理绘制事件、结果 Primitive、范围矩形、左下角面板和 Cesium Viewer。",[79,1534,1536],{"className":81,"code":1535,"language":83,"meta":84,"style":84},"drawTool.destroy()\nclearPreviousResult()\nheightmapPanel.destroy()\n\nif (!viewer.isDestroyed()) {\n  viewer.destroy()\n}\n",[41,1537,1538,1550,1557,1568,1572,1594,1604],{"__ignoreMap":84},[88,1539,1540,1543,1545,1548],{"class":90,"line":91},[88,1541,1542],{"class":94},"drawTool",[88,1544,99],{"class":98},[88,1546,1547],{"class":102},"destroy",[88,1549,1498],{"class":94},[88,1551,1552,1555],{"class":90,"line":143},[88,1553,1554],{"class":102},"clearPreviousResult",[88,1556,1498],{"class":94},[88,1558,1559,1562,1564,1566],{"class":90,"line":180},[88,1560,1561],{"class":94},"heightmapPanel",[88,1563,99],{"class":98},[88,1565,1547],{"class":102},[88,1567,1498],{"class":94},[88,1569,1570],{"class":90,"line":332},[88,1571,291],{"emptyLinePlaceholder":10},[88,1573,1574,1576,1579,1582,1584,1586,1589,1592],{"class":90,"line":504},[88,1575,603],{"class":296},[88,1577,1578],{"class":94}," (",[88,1580,1581],{"class":98},"!",[88,1583,1404],{"class":94},[88,1585,99],{"class":98},[88,1587,1588],{"class":102},"isDestroyed",[88,1590,1591],{"class":94},"()) ",[88,1593,617],{"class":98},[88,1595,1596,1598,1600,1602],{"class":90,"line":509},[88,1597,335],{"class":94},[88,1599,99],{"class":98},[88,1601,1547],{"class":102},[88,1603,1498],{"class":631},[88,1605,1606],{"class":90,"line":1325},[88,1607,657],{"class":98},[31,1609,1610,1611,1614,1615,1618],{},"这一点对内容站很重要：Demo 详情页通过路由切换反复挂载和卸载，如果不清理 ",[41,1612,1613],{},"ScreenSpaceEventHandler"," 或 ",[41,1616,1617],{},"Primitive","，很容易出现旧事件残留和显存占用增长。",[1620,1621,1622],"style",{},"html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":84,"searchDepth":143,"depth":143,"links":1624},[1625,1626,1627,1628,1629,1630,1631,1632,1633],{"id":29,"depth":143,"text":29},{"id":74,"depth":143,"text":74},{"id":246,"depth":143,"text":246},{"id":546,"depth":143,"text":546},{"id":783,"depth":143,"text":784},{"id":899,"depth":143,"text":899},{"id":1389,"depth":143,"text":1389},{"id":1461,"depth":143,"text":1461},{"id":1529,"depth":143,"text":1529},"2026-04-24","记录 Cesium 高度图 Demo 的 SDK 化实现，包括矩形绘制、最高可用地形层级解析、规则网格采样、灰度高度图生成、起伏 Primitive 构建和地形深度测试。","md","\u002Fimages\u002Fdemos\u002Fcesium-terrain-heightmap.svg",{},"\u002Fblog\u002Fcesium-terrain-heightmap-implementation",{"title":18,"description":1635},"blog\u002Fcesium-terrain-heightmap-implementation","KyVABdfDlZEGPAL_8GNhcxau8iEl3qNa87P4VHHDKX8",{"id":1644,"title":1645,"author":1646,"body":1648,"date":6538,"description":6539,"extension":1636,"image":6540,"meta":6541,"minRead":1376,"navigation":10,"path":6542,"seo":6543,"stem":6544,"__hash__":6545},"blog\u002Fblog\u002Fcesium-geojson-heatmap-sdk-implementation.md","Cesium GeoJSON 热力图 SDK 实现说明",{"name":20,"avatar":1647},{"src":22,"alt":20},{"type":24,"value":1649,"toc":6519},[1650,1652,1655,1678,1685,1689,1692,1977,1980,2006,2009,2156,2159,2162,2208,2211,2215,2226,2237,2243,2459,2470,2473,2567,2688,2691,2695,2708,2947,2950,2966,2970,2977,2980,3163,3169,3173,3180,3188,3191,3316,3319,3493,3496,3669,3680,3684,3693,3696,3839,3842,3969,3972,4321,4324,4342,4346,4349,4357,4363,4541,4546,4613,4619,4701,4712,4715,4718,4788,4791,5069,5072,5075,5078,5316,5319,5514,5517,5525,5529,5535,5555,5561,5565,5571,5726,5729,5746,5749,5752,5755,5847,5850,5861,5867,6044,6047,6051,6057,6128,6131,6148,6151,6158,6161,6167,6367,6370,6373,6492,6495,6498,6507,6510,6516],[27,1651,29],{"id":29},[31,1653,1654],{},"这个热力图实现的目标不是做一个只能服务某个 Demo 的页面效果，而是沉淀成一个通用 SDK 模块：",[35,1656,1657,1664,1667,1670,1675],{},[38,1658,1659,1660,1663],{},"业务侧只负责提供 ",[41,1661,1662],{},"FeatureCollection\u003CPoint>"," 格式的 GeoJSON 数据",[38,1665,1666],{},"SDK 内部负责从 GeoJSON 中解析经纬度和权重",[38,1668,1669],{},"SDK 内部负责把点数据绘制成二维 canvas 热力纹理",[38,1671,1672,1673],{},"SDK 内部负责把热力纹理绑定到 Cesium 的 ",[41,1674,905],{},[38,1676,1677],{},"半径、模糊度、透明度、显示目标、数据更新和销毁都通过统一 API 处理",[31,1679,1680,1681,1684],{},"所以这里最重要的设计边界是：Demo 页面只是使用者，真正可复用的能力在 ",[41,1682,1683],{},"demos\u002Fcesium\u002Fheatmap\u002F*"," 和 Cesium runtime 模块里。",[27,1686,1688],{"id":1687},"对外-api-形态","对外 API 形态",[31,1690,1691],{},"业务接入时先用统一 runtime 加载 Cesium，再创建 Viewer，最后把 GeoJSON 传给热力图图层工厂。",[79,1693,1695],{"className":81,"code":1694,"language":83,"meta":84,"style":84},"import { loadCesium } from '.\u002FcesiumRuntime.js'\nimport { createGeoJsonHeatmapLayer } from '.\u002Fheatmap\u002FcesiumHeatmapLayer.js'\n\nconst Cesium = await loadCesium()\nconst viewer = new Cesium.Viewer(container)\n\nconst heatmapLayer = createGeoJsonHeatmapLayer(viewer, {\n  geoJson,\n  bounds: {\n    west: 113.7,\n    south: 22.4,\n    east: 114.7,\n    north: 22.9\n  },\n  valueProperty: 'value',\n  radius: 42,\n  blur: 32,\n  minOpacity: 0.12,\n  maxOpacity: 0.86,\n  visible: true,\n  classificationTarget: 'both'\n})\n",[41,1696,1697,1721,1741,1745,1761,1782,1786,1804,1811,1820,1832,1844,1857,1868,1874,1891,1904,1917,1930,1943,1955,1970],{"__ignoreMap":84},[88,1698,1699,1702,1704,1707,1710,1713,1715,1718],{"class":90,"line":91},[88,1700,1701],{"class":296},"import",[88,1703,676],{"class":98},[88,1705,1706],{"class":94}," loadCesium",[88,1708,1709],{"class":98}," }",[88,1711,1712],{"class":296}," from",[88,1714,112],{"class":98},[88,1716,1717],{"class":115},".\u002FcesiumRuntime.js",[88,1719,1720],{"class":98},"'\n",[88,1722,1723,1725,1727,1730,1732,1734,1736,1739],{"class":90,"line":143},[88,1724,1701],{"class":296},[88,1726,676],{"class":98},[88,1728,1729],{"class":94}," createGeoJsonHeatmapLayer",[88,1731,1709],{"class":98},[88,1733,1712],{"class":296},[88,1735,112],{"class":98},[88,1737,1738],{"class":115},".\u002Fheatmap\u002FcesiumHeatmapLayer.js",[88,1740,1720],{"class":98},[88,1742,1743],{"class":90,"line":180},[88,1744,291],{"emptyLinePlaceholder":10},[88,1746,1747,1749,1752,1754,1757,1759],{"class":90,"line":332},[88,1748,264],{"class":263},[88,1750,1751],{"class":94}," Cesium ",[88,1753,270],{"class":98},[88,1755,1756],{"class":296}," await",[88,1758,1706],{"class":102},[88,1760,1498],{"class":94},[88,1762,1763,1765,1768,1770,1772,1774,1776,1779],{"class":90,"line":504},[88,1764,264],{"class":263},[88,1766,1767],{"class":94}," viewer ",[88,1769,270],{"class":98},[88,1771,514],{"class":98},[88,1773,517],{"class":94},[88,1775,99],{"class":98},[88,1777,1778],{"class":102},"Viewer",[88,1780,1781],{"class":94},"(container)\n",[88,1783,1784],{"class":90,"line":509},[88,1785,291],{"emptyLinePlaceholder":10},[88,1787,1788,1790,1793,1795,1797,1800,1802],{"class":90,"line":1325},[88,1789,264],{"class":263},[88,1791,1792],{"class":94}," heatmapLayer ",[88,1794,270],{"class":98},[88,1796,1729],{"class":102},[88,1798,1799],{"class":94},"(viewer",[88,1801,109],{"class":98},[88,1803,1152],{"class":98},[88,1805,1806,1809],{"class":90,"line":1345},[88,1807,1808],{"class":94},"  geoJson",[88,1810,1166],{"class":98},[88,1812,1813,1816,1818],{"class":90,"line":1357},[88,1814,1815],{"class":631},"  bounds",[88,1817,1160],{"class":98},[88,1819,1152],{"class":98},[88,1821,1822,1825,1827,1830],{"class":90,"line":1376},[88,1823,1824],{"class":631},"    west",[88,1826,1160],{"class":98},[88,1828,1829],{"class":634}," 113.7",[88,1831,1166],{"class":98},[88,1833,1834,1837,1839,1842],{"class":90,"line":1382},[88,1835,1836],{"class":631},"    south",[88,1838,1160],{"class":98},[88,1840,1841],{"class":634}," 22.4",[88,1843,1166],{"class":98},[88,1845,1847,1850,1852,1855],{"class":90,"line":1846},12,[88,1848,1849],{"class":631},"    east",[88,1851,1160],{"class":98},[88,1853,1854],{"class":634}," 114.7",[88,1856,1166],{"class":98},[88,1858,1860,1863,1865],{"class":90,"line":1859},13,[88,1861,1862],{"class":631},"    north",[88,1864,1160],{"class":98},[88,1866,1867],{"class":634}," 22.9\n",[88,1869,1871],{"class":90,"line":1870},14,[88,1872,1873],{"class":98},"  },\n",[88,1875,1877,1880,1882,1884,1887,1889],{"class":90,"line":1876},15,[88,1878,1879],{"class":631},"  valueProperty",[88,1881,1160],{"class":98},[88,1883,112],{"class":98},[88,1885,1886],{"class":115},"value",[88,1888,119],{"class":98},[88,1890,1166],{"class":98},[88,1892,1894,1897,1899,1902],{"class":90,"line":1893},16,[88,1895,1896],{"class":631},"  radius",[88,1898,1160],{"class":98},[88,1900,1901],{"class":634}," 42",[88,1903,1166],{"class":98},[88,1905,1907,1910,1912,1915],{"class":90,"line":1906},17,[88,1908,1909],{"class":631},"  blur",[88,1911,1160],{"class":98},[88,1913,1914],{"class":634}," 32",[88,1916,1166],{"class":98},[88,1918,1920,1923,1925,1928],{"class":90,"line":1919},18,[88,1921,1922],{"class":631},"  minOpacity",[88,1924,1160],{"class":98},[88,1926,1927],{"class":634}," 0.12",[88,1929,1166],{"class":98},[88,1931,1933,1936,1938,1941],{"class":90,"line":1932},19,[88,1934,1935],{"class":631},"  maxOpacity",[88,1937,1160],{"class":98},[88,1939,1940],{"class":634}," 0.86",[88,1942,1166],{"class":98},[88,1944,1946,1949,1951,1953],{"class":90,"line":1945},20,[88,1947,1948],{"class":631},"  visible",[88,1950,1160],{"class":98},[88,1952,1311],{"class":767},[88,1954,1166],{"class":98},[88,1956,1958,1961,1963,1965,1968],{"class":90,"line":1957},21,[88,1959,1960],{"class":631},"  classificationTarget",[88,1962,1160],{"class":98},[88,1964,112],{"class":98},[88,1966,1967],{"class":115},"both",[88,1969,1720],{"class":98},[88,1971,1973,1975],{"class":90,"line":1972},22,[88,1974,687],{"class":98},[88,1976,140],{"class":94},[31,1978,1979],{},"这个 API 有三个关键点：",[35,1981,1982,1989,1999],{},[38,1983,1984,1985,1988],{},"业务代码不需要把 ",[41,1986,1987],{},"Cesium"," 参数继续传进热力图模块。",[38,1990,1991,1992,1995,1996,1526],{},"覆盖范围通过 ",[41,1993,1994],{},"bounds"," 表达，SDK 只认 ",[41,1997,1998],{},"west\u002Fsouth\u002Feast\u002Fnorth",[38,2000,2001,2002,2005],{},"显示目标通过 ",[41,2003,2004],{},"'terrain' | 'tiles' | 'both'"," 控制，而不是额外绑一个 tileset 实例。",[31,2007,2008],{},"图层对象返回后，后续更新都走同一个对象：",[79,2010,2012],{"className":81,"code":2011,"language":83,"meta":84,"style":84},"heatmapLayer.updateGeoJson(nextGeoJson)\n\nheatmapLayer.setOptions({\n  radius: 56,\n  blur: 40,\n  minOpacity: 0.08,\n  maxOpacity: 0.9,\n  classificationTarget: 'tiles'\n})\n\nheatmapLayer.setVisible(false)\nheatmapLayer.setClassificationTarget('both')\nheatmapLayer.destroy()\n",[41,2013,2014,2027,2031,2044,2055,2066,2077,2088,2101,2107,2111,2127,2146],{"__ignoreMap":84},[88,2015,2016,2019,2021,2024],{"class":90,"line":91},[88,2017,2018],{"class":94},"heatmapLayer",[88,2020,99],{"class":98},[88,2022,2023],{"class":102},"updateGeoJson",[88,2025,2026],{"class":94},"(nextGeoJson)\n",[88,2028,2029],{"class":90,"line":143},[88,2030,291],{"emptyLinePlaceholder":10},[88,2032,2033,2035,2037,2040,2042],{"class":90,"line":180},[88,2034,2018],{"class":94},[88,2036,99],{"class":98},[88,2038,2039],{"class":102},"setOptions",[88,2041,130],{"class":94},[88,2043,617],{"class":98},[88,2045,2046,2048,2050,2053],{"class":90,"line":332},[88,2047,1896],{"class":631},[88,2049,1160],{"class":98},[88,2051,2052],{"class":634}," 56",[88,2054,1166],{"class":98},[88,2056,2057,2059,2061,2064],{"class":90,"line":504},[88,2058,1909],{"class":631},[88,2060,1160],{"class":98},[88,2062,2063],{"class":634}," 40",[88,2065,1166],{"class":98},[88,2067,2068,2070,2072,2075],{"class":90,"line":509},[88,2069,1922],{"class":631},[88,2071,1160],{"class":98},[88,2073,2074],{"class":634}," 0.08",[88,2076,1166],{"class":98},[88,2078,2079,2081,2083,2086],{"class":90,"line":1325},[88,2080,1935],{"class":631},[88,2082,1160],{"class":98},[88,2084,2085],{"class":634}," 0.9",[88,2087,1166],{"class":98},[88,2089,2090,2092,2094,2096,2099],{"class":90,"line":1345},[88,2091,1960],{"class":631},[88,2093,1160],{"class":98},[88,2095,112],{"class":98},[88,2097,2098],{"class":115},"tiles",[88,2100,1720],{"class":98},[88,2102,2103,2105],{"class":90,"line":1357},[88,2104,687],{"class":98},[88,2106,140],{"class":94},[88,2108,2109],{"class":90,"line":1376},[88,2110,291],{"emptyLinePlaceholder":10},[88,2112,2113,2115,2117,2120,2122,2125],{"class":90,"line":1382},[88,2114,2018],{"class":94},[88,2116,99],{"class":98},[88,2118,2119],{"class":102},"setVisible",[88,2121,130],{"class":94},[88,2123,2124],{"class":767},"false",[88,2126,140],{"class":94},[88,2128,2129,2131,2133,2136,2138,2140,2142,2144],{"class":90,"line":1846},[88,2130,2018],{"class":94},[88,2132,99],{"class":98},[88,2134,2135],{"class":102},"setClassificationTarget",[88,2137,130],{"class":94},[88,2139,119],{"class":98},[88,2141,1967],{"class":115},[88,2143,119],{"class":98},[88,2145,140],{"class":94},[88,2147,2148,2150,2152,2154],{"class":90,"line":1859},[88,2149,2018],{"class":94},[88,2151,99],{"class":98},[88,2153,1547],{"class":102},[88,2155,1498],{"class":94},[27,2157,2158],{"id":2158},"整体链路",[31,2160,2161],{},"当前实现可以按下面这条链路理解：",[219,2163,2164,2170,2175,2181,2187,2193,2202,2205],{},[38,2165,2166,2169],{},[41,2167,2168],{},"loadCesium()"," 统一加载 Cesium，并缓存 Cesium module。",[38,2171,2172,2173,1526],{},"Demo 或业务代码创建 Cesium ",[41,2174,1778],{},[38,2176,2177,2180],{},[41,2178,2179],{},"createGeoJsonHeatmapLayer(viewer, options)"," 创建热力图图层。",[38,2182,2183,2186],{},[41,2184,2185],{},"parseGeoJsonPoints()"," 校验 GeoJSON，并提取经纬度、权重和统计信息。",[38,2188,2189,2192],{},[41,2190,2191],{},"renderHeatmapCanvas()"," 把点位投影到 canvas，并生成热力纹理。",[38,2194,2195,2197,2198,2201],{},[41,2196,905],{}," 读取热力 canvas，并按 ",[41,2199,2200],{},"classificationTarget"," 分类到地形、3D Tiles 或两者。",[38,2203,2204],{},"参数或数据变化时，SDK 只重绘热力图，必要时重建 primitive。",[38,2206,2207],{},"页面销毁时，SDK 移除 primitive，并释放内部状态。",[31,2209,2210],{},"这条链路把“数据解析”“纹理绘制”“Cesium 绑定”“生命周期”拆开了。后续换真实接口数据、换业务范围、换权重字段时，不需要重写热力图渲染逻辑。",[27,2212,2214],{"id":2213},"cesium-runtime-为什么要单例化","Cesium runtime 为什么要单例化",[31,2216,2217,2218,2221,2222,2225],{},"之前每个 Cesium Demo 都自己设置 ",[41,2219,2220],{},"CESIUM_BASE_URL","、注入 Widgets CSS、动态导入 ",[41,2223,2224],{},"\u002Fcesium\u002Findex.js","。这些逻辑散在多个文件里，会带来两个问题：",[35,2227,2228,2231],{},[38,2229,2230],{},"每个 Demo 都有重复初始化代码。",[38,2232,2233,2234,2236],{},"热力图 SDK 如果也要求传入 ",[41,2235,1987],{},"，业务调用会变得啰嗦。",[31,2238,2239,2240,371],{},"现在统一收敛到 ",[41,2241,2242],{},"demos\u002Fcesium\u002FcesiumRuntime.js",[79,2244,2246],{"className":81,"code":2245,"language":83,"meta":84,"style":84},"let cesiumPromise = null\nlet cesiumModule = null\n\nexport async function loadCesium() {\n  ensureCesiumBaseUrl()\n  ensureCesiumWidgetsStyles()\n\n  if (!cesiumPromise) {\n    cesiumPromise = import(\u002F* @vite-ignore *\u002F CESIUM_RUNTIME_URL)\n      .then((module) => {\n        cesiumModule = module\n        return module\n      })\n      .catch((error) => {\n        cesiumPromise = null\n        throw error\n      })\n  }\n\n  return cesiumPromise\n}\n",[41,2247,2248,2261,2272,2276,2294,2301,2308,2312,2329,2350,2373,2383,2390,2397,2417,2426,2434,2440,2444,2448,2455],{"__ignoreMap":84},[88,2249,2250,2253,2256,2258],{"class":90,"line":91},[88,2251,2252],{"class":263},"let",[88,2254,2255],{"class":94}," cesiumPromise ",[88,2257,270],{"class":98},[88,2259,2260],{"class":98}," null\n",[88,2262,2263,2265,2268,2270],{"class":90,"line":143},[88,2264,2252],{"class":263},[88,2266,2267],{"class":94}," cesiumModule ",[88,2269,270],{"class":98},[88,2271,2260],{"class":98},[88,2273,2274],{"class":90,"line":180},[88,2275,291],{"emptyLinePlaceholder":10},[88,2277,2278,2281,2284,2287,2289,2292],{"class":90,"line":332},[88,2279,2280],{"class":296},"export",[88,2282,2283],{"class":263}," async",[88,2285,2286],{"class":263}," function",[88,2288,1706],{"class":102},[88,2290,2291],{"class":98},"()",[88,2293,1152],{"class":98},[88,2295,2296,2299],{"class":90,"line":504},[88,2297,2298],{"class":102},"  ensureCesiumBaseUrl",[88,2300,1498],{"class":631},[88,2302,2303,2306],{"class":90,"line":509},[88,2304,2305],{"class":102},"  ensureCesiumWidgetsStyles",[88,2307,1498],{"class":631},[88,2309,2310],{"class":90,"line":1325},[88,2311,291],{"emptyLinePlaceholder":10},[88,2313,2314,2317,2319,2321,2324,2327],{"class":90,"line":1345},[88,2315,2316],{"class":296},"  if",[88,2318,1578],{"class":631},[88,2320,1581],{"class":98},[88,2322,2323],{"class":94},"cesiumPromise",[88,2325,2326],{"class":631},") ",[88,2328,617],{"class":98},[88,2330,2331,2334,2336,2339,2341,2345,2348],{"class":90,"line":1357},[88,2332,2333],{"class":94},"    cesiumPromise",[88,2335,690],{"class":98},[88,2337,2338],{"class":102}," import",[88,2340,130],{"class":631},[88,2342,2344],{"class":2343},"sHwdD","\u002F* @vite-ignore *\u002F",[88,2346,2347],{"class":94}," CESIUM_RUNTIME_URL",[88,2349,140],{"class":631},[88,2351,2352,2355,2358,2360,2362,2366,2368,2371],{"class":90,"line":1376},[88,2353,2354],{"class":98},"      .",[88,2356,2357],{"class":102},"then",[88,2359,130],{"class":631},[88,2361,130],{"class":98},[88,2363,2365],{"class":2364},"sHdIc","module",[88,2367,122],{"class":98},[88,2369,2370],{"class":263}," =>",[88,2372,1152],{"class":98},[88,2374,2375,2378,2380],{"class":90,"line":1382},[88,2376,2377],{"class":94},"        cesiumModule",[88,2379,690],{"class":98},[88,2381,2382],{"class":98}," module\n",[88,2384,2385,2388],{"class":90,"line":1846},[88,2386,2387],{"class":296},"        return",[88,2389,2382],{"class":98},[88,2391,2392,2395],{"class":90,"line":1859},[88,2393,2394],{"class":98},"      }",[88,2396,140],{"class":631},[88,2398,2399,2401,2404,2406,2408,2411,2413,2415],{"class":90,"line":1870},[88,2400,2354],{"class":98},[88,2402,2403],{"class":102},"catch",[88,2405,130],{"class":631},[88,2407,130],{"class":98},[88,2409,2410],{"class":2364},"error",[88,2412,122],{"class":98},[88,2414,2370],{"class":263},[88,2416,1152],{"class":98},[88,2418,2419,2422,2424],{"class":90,"line":1876},[88,2420,2421],{"class":94},"        cesiumPromise",[88,2423,690],{"class":98},[88,2425,2260],{"class":98},[88,2427,2428,2431],{"class":90,"line":1893},[88,2429,2430],{"class":296},"        throw",[88,2432,2433],{"class":94}," error\n",[88,2435,2436,2438],{"class":90,"line":1906},[88,2437,2394],{"class":98},[88,2439,140],{"class":631},[88,2441,2442],{"class":90,"line":1919},[88,2443,1379],{"class":98},[88,2445,2446],{"class":90,"line":1932},[88,2447,291],{"emptyLinePlaceholder":10},[88,2449,2450,2452],{"class":90,"line":1945},[88,2451,622],{"class":296},[88,2453,2454],{"class":94}," cesiumPromise\n",[88,2456,2457],{"class":90,"line":1957},[88,2458,657],{"class":98},[31,2460,2461,2462,2465,2466,2469],{},"热力图模块内部通过 ",[41,2463,2464],{},"getCesium()"," 读取已经加载好的 Cesium。这样业务入口只需要保证先 ",[41,2467,2468],{},"await loadCesium()","，后面的通用模块就能直接工作。",[31,2471,2472],{},"为了让 Demo 能直接加载 Cesium World Terrain 和 OSM Buildings，runtime 现在还会在模块初始化时同步注入 Ion token：",[79,2474,2476],{"className":81,"code":2475,"language":83,"meta":84,"style":84},"function resolveCesiumIonToken() {\n  if (typeof window === 'undefined') {\n    return ''\n  }\n\n  return window.__NUXT__?.config?.public?.cesiumIonToken || ''\n}\n",[41,2477,2478,2490,2516,2524,2528,2532,2563],{"__ignoreMap":84},[88,2479,2480,2483,2486,2488],{"class":90,"line":91},[88,2481,2482],{"class":263},"function",[88,2484,2485],{"class":102}," resolveCesiumIonToken",[88,2487,2291],{"class":98},[88,2489,1152],{"class":98},[88,2491,2492,2494,2496,2499,2502,2505,2507,2510,2512,2514],{"class":90,"line":143},[88,2493,2316],{"class":296},[88,2495,1578],{"class":631},[88,2497,2498],{"class":98},"typeof",[88,2500,2501],{"class":94}," window",[88,2503,2504],{"class":98}," ===",[88,2506,112],{"class":98},[88,2508,2509],{"class":115},"undefined",[88,2511,119],{"class":98},[88,2513,2326],{"class":631},[88,2515,617],{"class":98},[88,2517,2518,2521],{"class":90,"line":180},[88,2519,2520],{"class":296},"    return",[88,2522,2523],{"class":98}," ''\n",[88,2525,2526],{"class":90,"line":332},[88,2527,1379],{"class":98},[88,2529,2530],{"class":90,"line":504},[88,2531,291],{"emptyLinePlaceholder":10},[88,2533,2534,2536,2538,2540,2543,2545,2548,2550,2553,2555,2558,2561],{"class":90,"line":509},[88,2535,622],{"class":296},[88,2537,2501],{"class":94},[88,2539,99],{"class":98},[88,2541,2542],{"class":94},"__NUXT__",[88,2544,569],{"class":98},[88,2546,2547],{"class":94},"config",[88,2549,569],{"class":98},[88,2551,2552],{"class":94},"public",[88,2554,569],{"class":98},[88,2556,2557],{"class":94},"cesiumIonToken",[88,2559,2560],{"class":98}," ||",[88,2562,2523],{"class":98},[88,2564,2565],{"class":90,"line":1325},[88,2566,657],{"class":98},[79,2568,2570],{"className":81,"code":2569,"language":83,"meta":84,"style":84},"cesiumPromise = import(\u002F* @vite-ignore *\u002F CESIUM_RUNTIME_URL)\n  .then((module) => {\n    const ionToken = resolveCesiumIonToken()\n\n    if (ionToken) {\n      module.Ion.defaultAccessToken = ionToken\n    }\n\n    cesiumModule = module\n    return module\n  })\n",[41,2571,2572,2588,2607,2621,2625,2639,2657,2662,2666,2675,2681],{"__ignoreMap":84},[88,2573,2574,2577,2579,2581,2583,2585],{"class":90,"line":91},[88,2575,2576],{"class":94},"cesiumPromise ",[88,2578,270],{"class":98},[88,2580,2338],{"class":102},[88,2582,130],{"class":94},[88,2584,2344],{"class":2343},[88,2586,2587],{"class":94}," CESIUM_RUNTIME_URL)\n",[88,2589,2590,2593,2595,2597,2599,2601,2603,2605],{"class":90,"line":143},[88,2591,2592],{"class":98},"  .",[88,2594,2357],{"class":102},[88,2596,130],{"class":94},[88,2598,130],{"class":98},[88,2600,2365],{"class":2364},[88,2602,122],{"class":98},[88,2604,2370],{"class":263},[88,2606,1152],{"class":98},[88,2608,2609,2612,2615,2617,2619],{"class":90,"line":180},[88,2610,2611],{"class":263},"    const",[88,2613,2614],{"class":94}," ionToken",[88,2616,690],{"class":98},[88,2618,2485],{"class":102},[88,2620,1498],{"class":631},[88,2622,2623],{"class":90,"line":332},[88,2624,291],{"emptyLinePlaceholder":10},[88,2626,2627,2630,2632,2635,2637],{"class":90,"line":504},[88,2628,2629],{"class":296},"    if",[88,2631,1578],{"class":631},[88,2633,2634],{"class":94},"ionToken",[88,2636,2326],{"class":631},[88,2638,617],{"class":98},[88,2640,2641,2644,2647,2649,2652,2654],{"class":90,"line":509},[88,2642,2643],{"class":98},"      module.",[88,2645,2646],{"class":94},"Ion",[88,2648,99],{"class":98},[88,2650,2651],{"class":94},"defaultAccessToken",[88,2653,690],{"class":98},[88,2655,2656],{"class":94}," ionToken\n",[88,2658,2659],{"class":90,"line":1325},[88,2660,2661],{"class":98},"    }\n",[88,2663,2664],{"class":90,"line":1345},[88,2665,291],{"emptyLinePlaceholder":10},[88,2667,2668,2671,2673],{"class":90,"line":1357},[88,2669,2670],{"class":94},"    cesiumModule",[88,2672,690],{"class":98},[88,2674,2382],{"class":98},[88,2676,2677,2679],{"class":90,"line":1376},[88,2678,2520],{"class":296},[88,2680,2382],{"class":98},[88,2682,2683,2686],{"class":90,"line":1382},[88,2684,2685],{"class":98},"  }",[88,2687,140],{"class":94},[31,2689,2690],{},"这样 Demo 页面里的 terrain \u002F 3D Tiles 加载，就不需要再到业务层单独处理 token 传递。",[27,2692,2694],{"id":2693},"geojson-数据约定","GeoJSON 数据约定",[31,2696,2697,2698,2700,2701,2704,2705,1526],{},"热力图输入固定使用 ",[41,2699,1662],{},"。坐标使用 WGS84，经纬度顺序为 ",[41,2702,2703],{},"[longitude, latitude]","。权重字段默认读取 ",[41,2706,2707],{},"properties.value",[79,2709,2713],{"className":2710,"code":2711,"language":2712,"meta":84,"style":84},"language-json shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"properties\": {\n        \"id\": \"heat-001\",\n        \"name\": \"sample point\",\n        \"category\": \"event\",\n        \"value\": 92\n      },\n      \"geometry\": {\n        \"type\": \"Point\",\n        \"coordinates\": [113.941, 22.541]\n      }\n    }\n  ]\n}\n","json",[41,2714,2715,2719,2742,2756,2761,2781,2794,2815,2834,2854,2867,2872,2885,2904,2929,2934,2938,2943],{"__ignoreMap":84},[88,2716,2717],{"class":90,"line":91},[88,2718,617],{"class":98},[88,2720,2721,2724,2727,2730,2732,2735,2738,2740],{"class":90,"line":143},[88,2722,2723],{"class":98},"  \"",[88,2725,2726],{"class":263},"type",[88,2728,2729],{"class":98},"\"",[88,2731,1160],{"class":98},[88,2733,2734],{"class":98}," \"",[88,2736,2737],{"class":115},"FeatureCollection",[88,2739,2729],{"class":98},[88,2741,1166],{"class":98},[88,2743,2744,2746,2749,2751,2753],{"class":90,"line":180},[88,2745,2723],{"class":98},[88,2747,2748],{"class":263},"features",[88,2750,2729],{"class":98},[88,2752,1160],{"class":98},[88,2754,2755],{"class":98}," [\n",[88,2757,2758],{"class":90,"line":332},[88,2759,2760],{"class":98},"    {\n",[88,2762,2763,2766,2768,2770,2772,2774,2777,2779],{"class":90,"line":504},[88,2764,2765],{"class":98},"      \"",[88,2767,2726],{"class":1242},[88,2769,2729],{"class":98},[88,2771,1160],{"class":98},[88,2773,2734],{"class":98},[88,2775,2776],{"class":115},"Feature",[88,2778,2729],{"class":98},[88,2780,1166],{"class":98},[88,2782,2783,2785,2788,2790,2792],{"class":90,"line":509},[88,2784,2765],{"class":98},[88,2786,2787],{"class":1242},"properties",[88,2789,2729],{"class":98},[88,2791,1160],{"class":98},[88,2793,1152],{"class":98},[88,2795,2796,2799,2802,2804,2806,2808,2811,2813],{"class":90,"line":1325},[88,2797,2798],{"class":98},"        \"",[88,2800,2801],{"class":634},"id",[88,2803,2729],{"class":98},[88,2805,1160],{"class":98},[88,2807,2734],{"class":98},[88,2809,2810],{"class":115},"heat-001",[88,2812,2729],{"class":98},[88,2814,1166],{"class":98},[88,2816,2817,2819,2821,2823,2825,2827,2830,2832],{"class":90,"line":1345},[88,2818,2798],{"class":98},[88,2820,127],{"class":634},[88,2822,2729],{"class":98},[88,2824,1160],{"class":98},[88,2826,2734],{"class":98},[88,2828,2829],{"class":115},"sample point",[88,2831,2729],{"class":98},[88,2833,1166],{"class":98},[88,2835,2836,2838,2841,2843,2845,2847,2850,2852],{"class":90,"line":1357},[88,2837,2798],{"class":98},[88,2839,2840],{"class":634},"category",[88,2842,2729],{"class":98},[88,2844,1160],{"class":98},[88,2846,2734],{"class":98},[88,2848,2849],{"class":115},"event",[88,2851,2729],{"class":98},[88,2853,1166],{"class":98},[88,2855,2856,2858,2860,2862,2864],{"class":90,"line":1376},[88,2857,2798],{"class":98},[88,2859,1886],{"class":634},[88,2861,2729],{"class":98},[88,2863,1160],{"class":98},[88,2865,2866],{"class":634}," 92\n",[88,2868,2869],{"class":90,"line":1382},[88,2870,2871],{"class":98},"      },\n",[88,2873,2874,2876,2879,2881,2883],{"class":90,"line":1846},[88,2875,2765],{"class":98},[88,2877,2878],{"class":1242},"geometry",[88,2880,2729],{"class":98},[88,2882,1160],{"class":98},[88,2884,1152],{"class":98},[88,2886,2887,2889,2891,2893,2895,2897,2900,2902],{"class":90,"line":1859},[88,2888,2798],{"class":98},[88,2890,2726],{"class":634},[88,2892,2729],{"class":98},[88,2894,1160],{"class":98},[88,2896,2734],{"class":98},[88,2898,2899],{"class":115},"Point",[88,2901,2729],{"class":98},[88,2903,1166],{"class":98},[88,2905,2906,2908,2911,2913,2915,2918,2921,2923,2926],{"class":90,"line":1870},[88,2907,2798],{"class":98},[88,2909,2910],{"class":634},"coordinates",[88,2912,2729],{"class":98},[88,2914,1160],{"class":98},[88,2916,2917],{"class":98}," [",[88,2919,2920],{"class":634},"113.941",[88,2922,109],{"class":98},[88,2924,2925],{"class":634}," 22.541",[88,2927,2928],{"class":98},"]\n",[88,2930,2931],{"class":90,"line":1876},[88,2932,2933],{"class":98},"      }\n",[88,2935,2936],{"class":90,"line":1893},[88,2937,2661],{"class":98},[88,2939,2940],{"class":90,"line":1906},[88,2941,2942],{"class":98},"  ]\n",[88,2944,2945],{"class":90,"line":1919},[88,2946,657],{"class":98},[31,2948,2949],{},"SDK 只依赖两部分数据：",[35,2951,2952,2958],{},[38,2953,2954,2957],{},[41,2955,2956],{},"geometry.coordinates","：点位经纬度",[38,2959,2960,2963,2964],{},[41,2961,2962],{},"properties[valueProperty]","：点位权重，默认是 ",[41,2965,2707],{},[27,2967,2969],{"id":2968},"geojson-怎么被解析","GeoJSON 怎么被解析",[31,2971,2972,2973,2976],{},"解析逻辑在 ",[41,2974,2975],{},"demos\u002Fcesium\u002Fheatmap\u002Fgeojson.js","。它不会因为某一个坏点中断整张热力图，而是跳过无效点，并返回统计信息。",[31,2978,2979],{},"关键判断如下：",[79,2981,2983],{"className":81,"code":2982,"language":83,"meta":84,"style":84},"const isPointFeature = feature?.type === 'Feature' &&\n  geometry?.type === 'Point' &&\n  Array.isArray(coordinates) &&\n  coordinates.length >= 2\n\nif (!isPointFeature || !Number.isFinite(coordinates[0]) || !Number.isFinite(coordinates[1]) || !Number.isFinite(value) || value \u003C 0) {\n  skipped += 1\n  continue\n}\n",[41,2984,2985,3014,3033,3049,3065,3069,3143,3154,3159],{"__ignoreMap":84},[88,2986,2987,2989,2992,2994,2997,2999,3002,3005,3007,3009,3011],{"class":90,"line":91},[88,2988,264],{"class":263},[88,2990,2991],{"class":94}," isPointFeature ",[88,2993,270],{"class":98},[88,2995,2996],{"class":94}," feature",[88,2998,569],{"class":98},[88,3000,3001],{"class":94},"type ",[88,3003,3004],{"class":98},"===",[88,3006,112],{"class":98},[88,3008,2776],{"class":115},[88,3010,119],{"class":98},[88,3012,3013],{"class":98}," &&\n",[88,3015,3016,3019,3021,3023,3025,3027,3029,3031],{"class":90,"line":143},[88,3017,3018],{"class":94},"  geometry",[88,3020,569],{"class":98},[88,3022,3001],{"class":94},[88,3024,3004],{"class":98},[88,3026,112],{"class":98},[88,3028,2899],{"class":115},[88,3030,119],{"class":98},[88,3032,3013],{"class":98},[88,3034,3035,3038,3040,3043,3046],{"class":90,"line":180},[88,3036,3037],{"class":94},"  Array",[88,3039,99],{"class":98},[88,3041,3042],{"class":102},"isArray",[88,3044,3045],{"class":94},"(coordinates) ",[88,3047,3048],{"class":98},"&&\n",[88,3050,3051,3054,3056,3059,3062],{"class":90,"line":332},[88,3052,3053],{"class":94},"  coordinates",[88,3055,99],{"class":98},[88,3057,3058],{"class":94},"length ",[88,3060,3061],{"class":98},">=",[88,3063,3064],{"class":634}," 2\n",[88,3066,3067],{"class":90,"line":504},[88,3068,291],{"emptyLinePlaceholder":10},[88,3070,3071,3073,3075,3077,3080,3083,3086,3089,3091,3093,3096,3098,3101,3103,3105,3107,3109,3111,3113,3115,3117,3119,3121,3123,3125,3127,3130,3132,3134,3137,3139,3141],{"class":90,"line":509},[88,3072,603],{"class":296},[88,3074,1578],{"class":94},[88,3076,1581],{"class":98},[88,3078,3079],{"class":94},"isPointFeature ",[88,3081,3082],{"class":98},"||",[88,3084,3085],{"class":98}," !",[88,3087,3088],{"class":94},"Number",[88,3090,99],{"class":98},[88,3092,611],{"class":102},[88,3094,3095],{"class":94},"(coordinates[",[88,3097,635],{"class":634},[88,3099,3100],{"class":94},"]) ",[88,3102,3082],{"class":98},[88,3104,3085],{"class":98},[88,3106,3088],{"class":94},[88,3108,99],{"class":98},[88,3110,611],{"class":102},[88,3112,3095],{"class":94},[88,3114,1187],{"class":634},[88,3116,3100],{"class":94},[88,3118,3082],{"class":98},[88,3120,3085],{"class":98},[88,3122,3088],{"class":94},[88,3124,99],{"class":98},[88,3126,611],{"class":102},[88,3128,3129],{"class":94},"(value) ",[88,3131,3082],{"class":98},[88,3133,864],{"class":94},[88,3135,3136],{"class":98},"\u003C",[88,3138,883],{"class":634},[88,3140,2326],{"class":94},[88,3142,617],{"class":98},[88,3144,3145,3148,3151],{"class":90,"line":1325},[88,3146,3147],{"class":94},"  skipped",[88,3149,3150],{"class":98}," +=",[88,3152,3153],{"class":634}," 1\n",[88,3155,3156],{"class":90,"line":1345},[88,3157,3158],{"class":296},"  continue\n",[88,3160,3161],{"class":90,"line":1357},[88,3162,657],{"class":98},[31,3164,3165,3166,3168],{},"然后再用 ",[41,3167,1994],{}," 判断点是否落在当前热力图覆盖范围内。这样业务接入时只需要明确覆盖范围，解析器就能稳定产出当前图层应该消费的点集。",[27,3170,3172],{"id":3171},"canvas-热力纹理怎么生成","Canvas 热力纹理怎么生成",[31,3174,3175,3176,3179],{},"真正生成热力图的是 ",[41,3177,3178],{},"demos\u002Fcesium\u002Fheatmap\u002FcanvasHeatmap.js","。它把热力图拆成两步：",[219,3181,3182,3185],{},[38,3183,3184],{},"先绘制灰度密度图",[38,3186,3187],{},"再把灰度 alpha 映射成热力颜色和透明度",[31,3189,3190],{},"生成单个点的影响范围时，使用径向渐变：",[79,3192,3194],{"className":81,"code":3193,"language":83,"meta":84,"style":84},"const innerRadius = clamp(radius - blur, 0, radius)\nconst gradient = context.createRadialGradient(center, center, innerRadius, center, center, radius)\ngradient.addColorStop(0, 'rgba(0, 0, 0, 1)')\ngradient.addColorStop(1, 'rgba(0, 0, 0, 0)')\n",[41,3195,3196,3226,3268,3293],{"__ignoreMap":84},[88,3197,3198,3200,3203,3205,3208,3211,3214,3217,3219,3221,3223],{"class":90,"line":91},[88,3199,264],{"class":263},[88,3201,3202],{"class":94}," innerRadius ",[88,3204,270],{"class":98},[88,3206,3207],{"class":102}," clamp",[88,3209,3210],{"class":94},"(radius ",[88,3212,3213],{"class":98},"-",[88,3215,3216],{"class":94}," blur",[88,3218,109],{"class":98},[88,3220,883],{"class":634},[88,3222,109],{"class":98},[88,3224,3225],{"class":94}," radius)\n",[88,3227,3228,3230,3233,3235,3238,3240,3243,3246,3248,3251,3253,3256,3258,3260,3262,3264,3266],{"class":90,"line":143},[88,3229,264],{"class":263},[88,3231,3232],{"class":94}," gradient ",[88,3234,270],{"class":98},[88,3236,3237],{"class":94}," context",[88,3239,99],{"class":98},[88,3241,3242],{"class":102},"createRadialGradient",[88,3244,3245],{"class":94},"(center",[88,3247,109],{"class":98},[88,3249,3250],{"class":94}," center",[88,3252,109],{"class":98},[88,3254,3255],{"class":94}," innerRadius",[88,3257,109],{"class":98},[88,3259,3250],{"class":94},[88,3261,109],{"class":98},[88,3263,3250],{"class":94},[88,3265,109],{"class":98},[88,3267,3225],{"class":94},[88,3269,3270,3273,3275,3278,3280,3282,3284,3286,3289,3291],{"class":90,"line":180},[88,3271,3272],{"class":94},"gradient",[88,3274,99],{"class":98},[88,3276,3277],{"class":102},"addColorStop",[88,3279,130],{"class":94},[88,3281,635],{"class":634},[88,3283,109],{"class":98},[88,3285,112],{"class":98},[88,3287,3288],{"class":115},"rgba(0, 0, 0, 1)",[88,3290,119],{"class":98},[88,3292,140],{"class":94},[88,3294,3295,3297,3299,3301,3303,3305,3307,3309,3312,3314],{"class":90,"line":332},[88,3296,3272],{"class":94},[88,3298,99],{"class":98},[88,3300,3277],{"class":102},[88,3302,130],{"class":94},[88,3304,1187],{"class":634},[88,3306,109],{"class":98},[88,3308,112],{"class":98},[88,3310,3311],{"class":115},"rgba(0, 0, 0, 0)",[88,3313,119],{"class":98},[88,3315,140],{"class":94},[31,3317,3318],{},"每个 GeoJSON 点绘制到灰度密度图时，会先按最大权重归一化：",[79,3320,3322],{"className":81,"code":3321,"language":83,"meta":84,"style":84},"const projected = projectLngLatToHeatmap(point.longitude, point.latitude, bounds, size.width, size.height)\nconst normalizedValue = point.value \u002F maxValue\n\ncontext.globalAlpha = clamp(normalizedValue, 0.02, 1)\ncontext.drawImage(\n  sprite,\n  projected.x - heatmapOptions.radius,\n  projected.y - heatmapOptions.radius\n)\n",[41,3323,3324,3376,3398,3402,3430,3442,3449,3471,3489],{"__ignoreMap":84},[88,3325,3326,3328,3331,3333,3336,3339,3341,3343,3345,3348,3350,3352,3354,3357,3359,3362,3364,3367,3369,3371,3373],{"class":90,"line":91},[88,3327,264],{"class":263},[88,3329,3330],{"class":94}," projected ",[88,3332,270],{"class":98},[88,3334,3335],{"class":102}," projectLngLatToHeatmap",[88,3337,3338],{"class":94},"(point",[88,3340,99],{"class":98},[88,3342,401],{"class":94},[88,3344,109],{"class":98},[88,3346,3347],{"class":94}," point",[88,3349,99],{"class":98},[88,3351,463],{"class":94},[88,3353,109],{"class":98},[88,3355,3356],{"class":94}," bounds",[88,3358,109],{"class":98},[88,3360,3361],{"class":94}," size",[88,3363,99],{"class":98},[88,3365,3366],{"class":94},"width",[88,3368,109],{"class":98},[88,3370,3361],{"class":94},[88,3372,99],{"class":98},[88,3374,3375],{"class":94},"height)\n",[88,3377,3378,3380,3383,3385,3387,3389,3392,3395],{"class":90,"line":143},[88,3379,264],{"class":263},[88,3381,3382],{"class":94}," normalizedValue ",[88,3384,270],{"class":98},[88,3386,3347],{"class":94},[88,3388,99],{"class":98},[88,3390,3391],{"class":94},"value ",[88,3393,3394],{"class":98},"\u002F",[88,3396,3397],{"class":94}," maxValue\n",[88,3399,3400],{"class":90,"line":180},[88,3401,291],{"emptyLinePlaceholder":10},[88,3403,3404,3407,3409,3412,3414,3416,3419,3421,3424,3426,3428],{"class":90,"line":332},[88,3405,3406],{"class":94},"context",[88,3408,99],{"class":98},[88,3410,3411],{"class":94},"globalAlpha ",[88,3413,270],{"class":98},[88,3415,3207],{"class":102},[88,3417,3418],{"class":94},"(normalizedValue",[88,3420,109],{"class":98},[88,3422,3423],{"class":634}," 0.02",[88,3425,109],{"class":98},[88,3427,1006],{"class":634},[88,3429,140],{"class":94},[88,3431,3432,3434,3436,3439],{"class":90,"line":504},[88,3433,3406],{"class":94},[88,3435,99],{"class":98},[88,3437,3438],{"class":102},"drawImage",[88,3440,3441],{"class":94},"(\n",[88,3443,3444,3447],{"class":90,"line":509},[88,3445,3446],{"class":94},"  sprite",[88,3448,1166],{"class":98},[88,3450,3451,3454,3456,3459,3461,3464,3466,3469],{"class":90,"line":1325},[88,3452,3453],{"class":94},"  projected",[88,3455,99],{"class":98},[88,3457,3458],{"class":94},"x ",[88,3460,3213],{"class":98},[88,3462,3463],{"class":94}," heatmapOptions",[88,3465,99],{"class":98},[88,3467,3468],{"class":94},"radius",[88,3470,1166],{"class":98},[88,3472,3473,3475,3477,3480,3482,3484,3486],{"class":90,"line":1345},[88,3474,3453],{"class":94},[88,3476,99],{"class":98},[88,3478,3479],{"class":94},"y ",[88,3481,3213],{"class":98},[88,3483,3463],{"class":94},[88,3485,99],{"class":98},[88,3487,3488],{"class":94},"radius\n",[88,3490,3491],{"class":90,"line":1357},[88,3492,140],{"class":94},[31,3494,3495],{},"最后再把 alpha 转成彩色热力图：",[79,3497,3499],{"className":81,"code":3498,"language":83,"meta":84,"style":84},"const intensity = pixels[index + 3] \u002F 255\nconst colorIndex = clampInteger(intensity * 255, 0, 255) * 4\nconst opacity = minOpacity + intensity * (maxOpacity - minOpacity)\n\npixels[index] = colorRamp[colorIndex]\npixels[index + 1] = colorRamp[colorIndex + 1]\npixels[index + 2] = colorRamp[colorIndex + 2]\npixels[index + 3] = clampInteger(opacity * 255, 0, 255)\n",[41,3500,3501,3524,3556,3582,3586,3596,3618,3638],{"__ignoreMap":84},[88,3502,3503,3505,3507,3509,3512,3514,3517,3519,3521],{"class":90,"line":91},[88,3504,264],{"class":263},[88,3506,830],{"class":94},[88,3508,270],{"class":98},[88,3510,3511],{"class":94}," pixels[index ",[88,3513,933],{"class":98},[88,3515,3516],{"class":634}," 3",[88,3518,1009],{"class":94},[88,3520,3394],{"class":98},[88,3522,3523],{"class":634}," 255\n",[88,3525,3526,3528,3531,3533,3535,3537,3539,3541,3543,3545,3547,3549,3551,3553],{"class":90,"line":143},[88,3527,264],{"class":263},[88,3529,3530],{"class":94}," colorIndex ",[88,3532,270],{"class":98},[88,3534,869],{"class":102},[88,3536,872],{"class":94},[88,3538,875],{"class":98},[88,3540,878],{"class":634},[88,3542,109],{"class":98},[88,3544,883],{"class":634},[88,3546,109],{"class":98},[88,3548,878],{"class":634},[88,3550,2326],{"class":94},[88,3552,875],{"class":98},[88,3554,3555],{"class":634}," 4\n",[88,3557,3558,3560,3563,3565,3568,3570,3572,3574,3577,3579],{"class":90,"line":180},[88,3559,264],{"class":263},[88,3561,3562],{"class":94}," opacity ",[88,3564,270],{"class":98},[88,3566,3567],{"class":94}," minOpacity ",[88,3569,933],{"class":98},[88,3571,830],{"class":94},[88,3573,875],{"class":98},[88,3575,3576],{"class":94}," (maxOpacity ",[88,3578,3213],{"class":98},[88,3580,3581],{"class":94}," minOpacity)\n",[88,3583,3584],{"class":90,"line":332},[88,3585,291],{"emptyLinePlaceholder":10},[88,3587,3588,3591,3593],{"class":90,"line":504},[88,3589,3590],{"class":94},"pixels[index] ",[88,3592,270],{"class":98},[88,3594,3595],{"class":94}," colorRamp[colorIndex]\n",[88,3597,3598,3601,3603,3605,3607,3609,3612,3614,3616],{"class":90,"line":509},[88,3599,3600],{"class":94},"pixels[index ",[88,3602,933],{"class":98},[88,3604,1006],{"class":634},[88,3606,1009],{"class":94},[88,3608,270],{"class":98},[88,3610,3611],{"class":94}," colorRamp[colorIndex ",[88,3613,933],{"class":98},[88,3615,1006],{"class":634},[88,3617,2928],{"class":94},[88,3619,3620,3622,3624,3626,3628,3630,3632,3634,3636],{"class":90,"line":1325},[88,3621,3600],{"class":94},[88,3623,933],{"class":98},[88,3625,1027],{"class":634},[88,3627,1009],{"class":94},[88,3629,270],{"class":98},[88,3631,3611],{"class":94},[88,3633,933],{"class":98},[88,3635,1027],{"class":634},[88,3637,2928],{"class":94},[88,3639,3640,3642,3644,3646,3648,3650,3652,3655,3657,3659,3661,3663,3665,3667],{"class":90,"line":1345},[88,3641,3600],{"class":94},[88,3643,933],{"class":98},[88,3645,3516],{"class":634},[88,3647,1009],{"class":94},[88,3649,270],{"class":98},[88,3651,869],{"class":102},[88,3653,3654],{"class":94},"(opacity ",[88,3656,875],{"class":98},[88,3658,878],{"class":634},[88,3660,109],{"class":98},[88,3662,883],{"class":634},[88,3664,109],{"class":98},[88,3666,878],{"class":634},[88,3668,140],{"class":94},[31,3670,3671,3672,3675,3676,3679],{},"所以 ",[41,3673,3674],{},"minOpacity"," 和 ",[41,3677,3678],{},"maxOpacity"," 不是简单设置整个图层透明度，而是在每个像素颜色映射阶段参与计算。",[27,3681,3683],{"id":3682},"groundprimitive-怎么绑定热力图","GroundPrimitive 怎么绑定热力图",[31,3685,3686,3687,3690,3691,1526],{},"这次改造的核心，就是把原来的 ",[41,3688,3689],{},"SingleTileImageryProvider"," 改成 ",[41,3692,905],{},[31,3694,3695],{},"首先会做能力检查：",[79,3697,3699],{"className":81,"code":3698,"language":83,"meta":84,"style":84},"function ensureGroundPrimitiveSupport(Cesium, viewer) {\n  if (!Cesium.GroundPrimitive.isSupported(viewer.scene)) {\n    throw new Error('当前环境不支持 GroundPrimitive，无法渲染热力图。')\n  }\n\n  if (!Cesium.GroundPrimitive.supportsMaterials(viewer.scene)) {\n    throw new Error('当前环境不支持 GroundPrimitive 材质，无法渲染热力图。')\n  }\n}\n",[41,3700,3701,3720,3752,3773,3777,3781,3812,3831,3835],{"__ignoreMap":84},[88,3702,3703,3705,3708,3710,3712,3714,3716,3718],{"class":90,"line":91},[88,3704,2482],{"class":263},[88,3706,3707],{"class":102}," ensureGroundPrimitiveSupport",[88,3709,130],{"class":98},[88,3711,1987],{"class":2364},[88,3713,109],{"class":98},[88,3715,273],{"class":2364},[88,3717,122],{"class":98},[88,3719,1152],{"class":98},[88,3721,3722,3724,3726,3728,3730,3732,3734,3736,3739,3741,3743,3745,3747,3750],{"class":90,"line":143},[88,3723,2316],{"class":296},[88,3725,1578],{"class":631},[88,3727,1581],{"class":98},[88,3729,1987],{"class":94},[88,3731,99],{"class":98},[88,3733,905],{"class":94},[88,3735,99],{"class":98},[88,3737,3738],{"class":102},"isSupported",[88,3740,130],{"class":631},[88,3742,1404],{"class":94},[88,3744,99],{"class":98},[88,3746,304],{"class":94},[88,3748,3749],{"class":631},")) ",[88,3751,617],{"class":98},[88,3753,3754,3757,3759,3762,3764,3766,3769,3771],{"class":90,"line":180},[88,3755,3756],{"class":296},"    throw",[88,3758,514],{"class":98},[88,3760,3761],{"class":102}," Error",[88,3763,130],{"class":631},[88,3765,119],{"class":98},[88,3767,3768],{"class":115},"当前环境不支持 GroundPrimitive，无法渲染热力图。",[88,3770,119],{"class":98},[88,3772,140],{"class":631},[88,3774,3775],{"class":90,"line":332},[88,3776,1379],{"class":98},[88,3778,3779],{"class":90,"line":504},[88,3780,291],{"emptyLinePlaceholder":10},[88,3782,3783,3785,3787,3789,3791,3793,3795,3797,3800,3802,3804,3806,3808,3810],{"class":90,"line":509},[88,3784,2316],{"class":296},[88,3786,1578],{"class":631},[88,3788,1581],{"class":98},[88,3790,1987],{"class":94},[88,3792,99],{"class":98},[88,3794,905],{"class":94},[88,3796,99],{"class":98},[88,3798,3799],{"class":102},"supportsMaterials",[88,3801,130],{"class":631},[88,3803,1404],{"class":94},[88,3805,99],{"class":98},[88,3807,304],{"class":94},[88,3809,3749],{"class":631},[88,3811,617],{"class":98},[88,3813,3814,3816,3818,3820,3822,3824,3827,3829],{"class":90,"line":1325},[88,3815,3756],{"class":296},[88,3817,514],{"class":98},[88,3819,3761],{"class":102},[88,3821,130],{"class":631},[88,3823,119],{"class":98},[88,3825,3826],{"class":115},"当前环境不支持 GroundPrimitive 材质，无法渲染热力图。",[88,3828,119],{"class":98},[88,3830,140],{"class":631},[88,3832,3833],{"class":90,"line":1345},[88,3834,1379],{"class":98},[88,3836,3837],{"class":90,"line":1357},[88,3838,657],{"class":98},[31,3840,3841],{},"然后把 canvas 纹理包装成 Image material：",[79,3843,3845],{"className":81,"code":3844,"language":83,"meta":84,"style":84},"function createGroundMaterial(Cesium, canvas) {\n  return Cesium.Material.fromType('Image', {\n    image: canvas,\n    transparent: true,\n    repeat: new Cesium.Cartesian2(1, 1),\n    color: Cesium.Color.WHITE\n  })\n}\n",[41,3846,3847,3866,3892,3903,3914,3941,3959,3965],{"__ignoreMap":84},[88,3848,3849,3851,3854,3856,3858,3860,3862,3864],{"class":90,"line":91},[88,3850,2482],{"class":263},[88,3852,3853],{"class":102}," createGroundMaterial",[88,3855,130],{"class":98},[88,3857,1987],{"class":2364},[88,3859,109],{"class":98},[88,3861,1163],{"class":2364},[88,3863,122],{"class":98},[88,3865,1152],{"class":98},[88,3867,3868,3870,3872,3874,3876,3878,3880,3882,3884,3886,3888,3890],{"class":90,"line":143},[88,3869,622],{"class":296},[88,3871,517],{"class":94},[88,3873,99],{"class":98},[88,3875,1134],{"class":94},[88,3877,99],{"class":98},[88,3879,1139],{"class":102},[88,3881,130],{"class":631},[88,3883,119],{"class":98},[88,3885,895],{"class":115},[88,3887,119],{"class":98},[88,3889,109],{"class":98},[88,3891,1152],{"class":98},[88,3893,3894,3897,3899,3901],{"class":90,"line":180},[88,3895,3896],{"class":631},"    image",[88,3898,1160],{"class":98},[88,3900,1163],{"class":94},[88,3902,1166],{"class":98},[88,3904,3905,3908,3910,3912],{"class":90,"line":332},[88,3906,3907],{"class":631},"    transparent",[88,3909,1160],{"class":98},[88,3911,1311],{"class":767},[88,3913,1166],{"class":98},[88,3915,3916,3919,3921,3923,3925,3927,3929,3931,3933,3935,3937,3939],{"class":90,"line":504},[88,3917,3918],{"class":631},"    repeat",[88,3920,1160],{"class":98},[88,3922,514],{"class":98},[88,3924,517],{"class":94},[88,3926,99],{"class":98},[88,3928,1182],{"class":102},[88,3930,130],{"class":631},[88,3932,1187],{"class":634},[88,3934,109],{"class":98},[88,3936,1006],{"class":634},[88,3938,122],{"class":631},[88,3940,1166],{"class":98},[88,3942,3943,3946,3948,3950,3952,3954,3956],{"class":90,"line":509},[88,3944,3945],{"class":631},"    color",[88,3947,1160],{"class":98},[88,3949,517],{"class":94},[88,3951,99],{"class":98},[88,3953,1209],{"class":94},[88,3955,99],{"class":98},[88,3957,3958],{"class":94},"WHITE\n",[88,3960,3961,3963],{"class":90,"line":1325},[88,3962,2685],{"class":98},[88,3964,140],{"class":631},[88,3966,3967],{"class":90,"line":1345},[88,3968,657],{"class":98},[31,3970,3971],{},"真正创建 primitive 的逻辑如下：",[79,3973,3975],{"className":81,"code":3974,"language":83,"meta":84,"style":84},"function createGroundPrimitive(Cesium, state) {\n  const material = createGroundMaterial(Cesium, state.renderResult.canvas)\n  const appearance = new Cesium.EllipsoidSurfaceAppearance({\n    material,\n    translucent: true,\n    aboveGround: false\n  })\n  const primitive = new Cesium.GroundPrimitive({\n    geometryInstances: new Cesium.GeometryInstance({\n      geometry: new Cesium.RectangleGeometry({\n        rectangle: createCesiumRectangle(Cesium, state.bounds),\n        vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT\n      }),\n      id: 'geojson-heatmap-ground-primitive'\n    }),\n    appearance,\n    classificationType: toClassificationType(Cesium, state.classificationTarget)\n  })\n\n  primitive.show = state.visible\n\n  return {\n    primitive,\n    material,\n    appearance\n  }\n}\n",[41,3976,3977,3997,4029,4051,4058,4069,4078,4084,4105,4125,4145,4171,4189,4197,4211,4220,4227,4251,4257,4261,4280,4284,4290,4298,4305,4311,4316],{"__ignoreMap":84},[88,3978,3979,3981,3984,3986,3988,3990,3993,3995],{"class":90,"line":91},[88,3980,2482],{"class":263},[88,3982,3983],{"class":102}," createGroundPrimitive",[88,3985,130],{"class":98},[88,3987,1987],{"class":2364},[88,3989,109],{"class":98},[88,3991,3992],{"class":2364}," state",[88,3994,122],{"class":98},[88,3996,1152],{"class":98},[88,3998,3999,4002,4005,4007,4009,4011,4013,4015,4017,4019,4022,4024,4027],{"class":90,"line":143},[88,4000,4001],{"class":263},"  const",[88,4003,4004],{"class":94}," material",[88,4006,690],{"class":98},[88,4008,3853],{"class":102},[88,4010,130],{"class":631},[88,4012,1987],{"class":94},[88,4014,109],{"class":98},[88,4016,3992],{"class":94},[88,4018,99],{"class":98},[88,4020,4021],{"class":94},"renderResult",[88,4023,99],{"class":98},[88,4025,4026],{"class":94},"canvas",[88,4028,140],{"class":631},[88,4030,4031,4033,4036,4038,4040,4042,4044,4047,4049],{"class":90,"line":180},[88,4032,4001],{"class":263},[88,4034,4035],{"class":94}," appearance",[88,4037,690],{"class":98},[88,4039,514],{"class":98},[88,4041,517],{"class":94},[88,4043,99],{"class":98},[88,4045,4046],{"class":102},"EllipsoidSurfaceAppearance",[88,4048,130],{"class":631},[88,4050,617],{"class":98},[88,4052,4053,4056],{"class":90,"line":332},[88,4054,4055],{"class":94},"    material",[88,4057,1166],{"class":98},[88,4059,4060,4063,4065,4067],{"class":90,"line":504},[88,4061,4062],{"class":631},"    translucent",[88,4064,1160],{"class":98},[88,4066,1311],{"class":767},[88,4068,1166],{"class":98},[88,4070,4071,4074,4076],{"class":90,"line":509},[88,4072,4073],{"class":631},"    aboveGround",[88,4075,1160],{"class":98},[88,4077,1226],{"class":767},[88,4079,4080,4082],{"class":90,"line":1325},[88,4081,2685],{"class":98},[88,4083,140],{"class":631},[88,4085,4086,4088,4091,4093,4095,4097,4099,4101,4103],{"class":90,"line":1345},[88,4087,4001],{"class":263},[88,4089,4090],{"class":94}," primitive",[88,4092,690],{"class":98},[88,4094,514],{"class":98},[88,4096,517],{"class":94},[88,4098,99],{"class":98},[88,4100,905],{"class":102},[88,4102,130],{"class":631},[88,4104,617],{"class":98},[88,4106,4107,4110,4112,4114,4116,4118,4121,4123],{"class":90,"line":1357},[88,4108,4109],{"class":631},"    geometryInstances",[88,4111,1160],{"class":98},[88,4113,514],{"class":98},[88,4115,517],{"class":94},[88,4117,99],{"class":98},[88,4119,4120],{"class":102},"GeometryInstance",[88,4122,130],{"class":631},[88,4124,617],{"class":98},[88,4126,4127,4130,4132,4134,4136,4138,4141,4143],{"class":90,"line":1376},[88,4128,4129],{"class":631},"      geometry",[88,4131,1160],{"class":98},[88,4133,514],{"class":98},[88,4135,517],{"class":94},[88,4137,99],{"class":98},[88,4139,4140],{"class":102},"RectangleGeometry",[88,4142,130],{"class":631},[88,4144,617],{"class":98},[88,4146,4147,4150,4152,4155,4157,4159,4161,4163,4165,4167,4169],{"class":90,"line":1382},[88,4148,4149],{"class":631},"        rectangle",[88,4151,1160],{"class":98},[88,4153,4154],{"class":102}," createCesiumRectangle",[88,4156,130],{"class":631},[88,4158,1987],{"class":94},[88,4160,109],{"class":98},[88,4162,3992],{"class":94},[88,4164,99],{"class":98},[88,4166,1994],{"class":94},[88,4168,122],{"class":631},[88,4170,1166],{"class":98},[88,4172,4173,4176,4178,4180,4182,4184,4186],{"class":90,"line":1846},[88,4174,4175],{"class":631},"        vertexFormat",[88,4177,1160],{"class":98},[88,4179,517],{"class":94},[88,4181,99],{"class":98},[88,4183,4046],{"class":94},[88,4185,99],{"class":98},[88,4187,4188],{"class":94},"VERTEX_FORMAT\n",[88,4190,4191,4193,4195],{"class":90,"line":1859},[88,4192,2394],{"class":98},[88,4194,122],{"class":631},[88,4196,1166],{"class":98},[88,4198,4199,4202,4204,4206,4209],{"class":90,"line":1870},[88,4200,4201],{"class":631},"      id",[88,4203,1160],{"class":98},[88,4205,112],{"class":98},[88,4207,4208],{"class":115},"geojson-heatmap-ground-primitive",[88,4210,1720],{"class":98},[88,4212,4213,4216,4218],{"class":90,"line":1876},[88,4214,4215],{"class":98},"    }",[88,4217,122],{"class":631},[88,4219,1166],{"class":98},[88,4221,4222,4225],{"class":90,"line":1893},[88,4223,4224],{"class":94},"    appearance",[88,4226,1166],{"class":98},[88,4228,4229,4232,4234,4237,4239,4241,4243,4245,4247,4249],{"class":90,"line":1906},[88,4230,4231],{"class":631},"    classificationType",[88,4233,1160],{"class":98},[88,4235,4236],{"class":102}," toClassificationType",[88,4238,130],{"class":631},[88,4240,1987],{"class":94},[88,4242,109],{"class":98},[88,4244,3992],{"class":94},[88,4246,99],{"class":98},[88,4248,2200],{"class":94},[88,4250,140],{"class":631},[88,4252,4253,4255],{"class":90,"line":1919},[88,4254,2685],{"class":98},[88,4256,140],{"class":631},[88,4258,4259],{"class":90,"line":1932},[88,4260,291],{"emptyLinePlaceholder":10},[88,4262,4263,4266,4268,4271,4273,4275,4277],{"class":90,"line":1945},[88,4264,4265],{"class":94},"  primitive",[88,4267,99],{"class":98},[88,4269,4270],{"class":94},"show",[88,4272,690],{"class":98},[88,4274,3992],{"class":94},[88,4276,99],{"class":98},[88,4278,4279],{"class":94},"visible\n",[88,4281,4282],{"class":90,"line":1957},[88,4283,291],{"emptyLinePlaceholder":10},[88,4285,4286,4288],{"class":90,"line":1972},[88,4287,622],{"class":296},[88,4289,1152],{"class":98},[88,4291,4293,4296],{"class":90,"line":4292},23,[88,4294,4295],{"class":94},"    primitive",[88,4297,1166],{"class":98},[88,4299,4301,4303],{"class":90,"line":4300},24,[88,4302,4055],{"class":94},[88,4304,1166],{"class":98},[88,4306,4308],{"class":90,"line":4307},25,[88,4309,4310],{"class":94},"    appearance\n",[88,4312,4314],{"class":90,"line":4313},26,[88,4315,1379],{"class":98},[88,4317,4319],{"class":90,"line":4318},27,[88,4320,657],{"class":98},[31,4322,4323],{},"这里的职责边界很清楚：",[35,4325,4326,4331,4337],{},[38,4327,4328,4330],{},[41,4329,4140],{}," 定义热力图的经纬度覆盖范围",[38,4332,4333,4336],{},[41,4334,4335],{},"Image material"," 负责把 canvas 结果变成可采样纹理",[38,4338,4339,4341],{},[41,4340,905],{}," 负责把这张热力图分类到 Cesium 场景表面",[27,4343,4345],{"id":4344},"demo-怎么补地形和-3d-tiles-测试场景","Demo 怎么补地形和 3D Tiles 测试场景",[31,4347,4348],{},"为了验证热力图是否真的同时贴到地形和模型，Demo runtime 会额外加载两层场景资源：",[219,4350,4351,4354],{},[38,4352,4353],{},"Cesium World Terrain",[38,4355,4356],{},"Cesium OSM Buildings",[31,4358,4359,4360,371],{},"加载逻辑集中在 ",[41,4361,4362],{},"loadTerrainAndTiles()",[79,4364,4366],{"className":81,"code":4365,"language":83,"meta":84,"style":84},"async function loadTerrainAndTiles(Cesium, viewer) {\n  const results = await Promise.allSettled([\n    Cesium.createWorldTerrainAsync({\n      requestWaterMask: true,\n      requestVertexNormals: true\n    }),\n    Cesium.createOsmBuildingsAsync({\n      defaultColor: Cesium.Color.fromCssColorString('#d9e0ea').withAlpha(0.92),\n      enableShowOutline: false,\n      showOutline: false\n    })\n  ])\n",[41,4367,4368,4390,4412,4426,4437,4446,4454,4467,4510,4521,4530,4536],{"__ignoreMap":84},[88,4369,4370,4373,4375,4378,4380,4382,4384,4386,4388],{"class":90,"line":91},[88,4371,4372],{"class":263},"async",[88,4374,2286],{"class":263},[88,4376,4377],{"class":102}," loadTerrainAndTiles",[88,4379,130],{"class":98},[88,4381,1987],{"class":2364},[88,4383,109],{"class":98},[88,4385,273],{"class":2364},[88,4387,122],{"class":98},[88,4389,1152],{"class":98},[88,4391,4392,4394,4397,4399,4401,4404,4406,4409],{"class":90,"line":143},[88,4393,4001],{"class":263},[88,4395,4396],{"class":94}," results",[88,4398,690],{"class":98},[88,4400,1756],{"class":296},[88,4402,4403],{"class":1242}," Promise",[88,4405,99],{"class":98},[88,4407,4408],{"class":102},"allSettled",[88,4410,4411],{"class":631},"([\n",[88,4413,4414,4417,4419,4422,4424],{"class":90,"line":180},[88,4415,4416],{"class":94},"    Cesium",[88,4418,99],{"class":98},[88,4420,4421],{"class":102},"createWorldTerrainAsync",[88,4423,130],{"class":631},[88,4425,617],{"class":98},[88,4427,4428,4431,4433,4435],{"class":90,"line":332},[88,4429,4430],{"class":631},"      requestWaterMask",[88,4432,1160],{"class":98},[88,4434,1311],{"class":767},[88,4436,1166],{"class":98},[88,4438,4439,4442,4444],{"class":90,"line":504},[88,4440,4441],{"class":631},"      requestVertexNormals",[88,4443,1160],{"class":98},[88,4445,1451],{"class":767},[88,4447,4448,4450,4452],{"class":90,"line":509},[88,4449,4215],{"class":98},[88,4451,122],{"class":631},[88,4453,1166],{"class":98},[88,4455,4456,4458,4460,4463,4465],{"class":90,"line":1325},[88,4457,4416],{"class":94},[88,4459,99],{"class":98},[88,4461,4462],{"class":102},"createOsmBuildingsAsync",[88,4464,130],{"class":631},[88,4466,617],{"class":98},[88,4468,4469,4472,4474,4476,4478,4480,4482,4485,4487,4489,4492,4494,4496,4498,4501,4503,4506,4508],{"class":90,"line":1345},[88,4470,4471],{"class":631},"      defaultColor",[88,4473,1160],{"class":98},[88,4475,517],{"class":94},[88,4477,99],{"class":98},[88,4479,1209],{"class":94},[88,4481,99],{"class":98},[88,4483,4484],{"class":102},"fromCssColorString",[88,4486,130],{"class":631},[88,4488,119],{"class":98},[88,4490,4491],{"class":115},"#d9e0ea",[88,4493,119],{"class":98},[88,4495,122],{"class":631},[88,4497,99],{"class":98},[88,4499,4500],{"class":102},"withAlpha",[88,4502,130],{"class":631},[88,4504,4505],{"class":634},"0.92",[88,4507,122],{"class":631},[88,4509,1166],{"class":98},[88,4511,4512,4515,4517,4519],{"class":90,"line":1357},[88,4513,4514],{"class":631},"      enableShowOutline",[88,4516,1160],{"class":98},[88,4518,768],{"class":767},[88,4520,1166],{"class":98},[88,4522,4523,4526,4528],{"class":90,"line":1376},[88,4524,4525],{"class":631},"      showOutline",[88,4527,1160],{"class":98},[88,4529,1226],{"class":767},[88,4531,4532,4534],{"class":90,"line":1382},[88,4533,4215],{"class":98},[88,4535,140],{"class":631},[88,4537,4538],{"class":90,"line":1846},[88,4539,4540],{"class":631},"  ])\n",[31,4542,4543,4544,371],{},"地形成功后，直接挂到 ",[41,4545,1469],{},[79,4547,4549],{"className":81,"code":4548,"language":83,"meta":84,"style":84},"if (terrainResult?.status === 'fulfilled') {\n  viewer.terrainProvider = terrainResult.value\n  sceneState.terrainReady = true\n}\n",[41,4550,4551,4576,4595,4609],{"__ignoreMap":84},[88,4552,4553,4555,4558,4560,4563,4565,4567,4570,4572,4574],{"class":90,"line":91},[88,4554,603],{"class":296},[88,4556,4557],{"class":94}," (terrainResult",[88,4559,569],{"class":98},[88,4561,4562],{"class":94},"status ",[88,4564,3004],{"class":98},[88,4566,112],{"class":98},[88,4568,4569],{"class":115},"fulfilled",[88,4571,119],{"class":98},[88,4573,2326],{"class":94},[88,4575,617],{"class":98},[88,4577,4578,4580,4582,4585,4587,4590,4592],{"class":90,"line":143},[88,4579,335],{"class":94},[88,4581,99],{"class":98},[88,4583,4584],{"class":94},"terrainProvider",[88,4586,690],{"class":98},[88,4588,4589],{"class":94}," terrainResult",[88,4591,99],{"class":98},[88,4593,4594],{"class":94},"value\n",[88,4596,4597,4600,4602,4605,4607],{"class":90,"line":180},[88,4598,4599],{"class":94},"  sceneState",[88,4601,99],{"class":98},[88,4603,4604],{"class":94},"terrainReady",[88,4606,690],{"class":98},[88,4608,1451],{"class":767},[88,4610,4611],{"class":90,"line":332},[88,4612,657],{"class":98},[31,4614,4615,4616,371],{},"OSM Buildings 成功后，直接加到 ",[41,4617,4618],{},"viewer.scene.primitives",[79,4620,4622],{"className":81,"code":4621,"language":83,"meta":84,"style":84},"if (tilesResult?.status === 'fulfilled') {\n  sceneState.tileset = viewer.scene.primitives.add(tilesResult.value)\n  sceneState.tilesReady = true\n}\n",[41,4623,4624,4647,4684,4697],{"__ignoreMap":84},[88,4625,4626,4628,4631,4633,4635,4637,4639,4641,4643,4645],{"class":90,"line":91},[88,4627,603],{"class":296},[88,4629,4630],{"class":94}," (tilesResult",[88,4632,569],{"class":98},[88,4634,4562],{"class":94},[88,4636,3004],{"class":98},[88,4638,112],{"class":98},[88,4640,4569],{"class":115},[88,4642,119],{"class":98},[88,4644,2326],{"class":94},[88,4646,617],{"class":98},[88,4648,4649,4651,4653,4656,4658,4660,4662,4664,4666,4669,4671,4673,4675,4678,4680,4682],{"class":90,"line":143},[88,4650,4599],{"class":94},[88,4652,99],{"class":98},[88,4654,4655],{"class":94},"tileset",[88,4657,690],{"class":98},[88,4659,273],{"class":94},[88,4661,99],{"class":98},[88,4663,304],{"class":94},[88,4665,99],{"class":98},[88,4667,4668],{"class":94},"primitives",[88,4670,99],{"class":98},[88,4672,103],{"class":102},[88,4674,130],{"class":631},[88,4676,4677],{"class":94},"tilesResult",[88,4679,99],{"class":98},[88,4681,1886],{"class":94},[88,4683,140],{"class":631},[88,4685,4686,4688,4690,4693,4695],{"class":90,"line":180},[88,4687,4599],{"class":94},[88,4689,99],{"class":98},[88,4691,4692],{"class":94},"tilesReady",[88,4694,690],{"class":98},[88,4696,1451],{"class":767},[88,4698,4699],{"class":90,"line":332},[88,4700,657],{"class":98},[31,4702,4703,4704,4707,4708,4711],{},"这里不用把热力图和 tileset 再做额外绑定，因为当前热力图本身就是 ",[41,4705,4706],{},"GroundPrimitive classification","。只要 tileset 已经进入场景，",[41,4709,4710],{},"classificationTarget = 'tiles' | 'both'"," 就会直接作用到模型表面。",[27,4713,4714],{"id":4714},"为什么要选一个热力图测试点",[31,4716,4717],{},"只把地形和 3D Tiles 加进场景还不够，观察上也需要一个固定锚点。Demo 现在会从当前 GeoJSON 中选一个离参考城市点最近的真实热力点，作为“热力图测试点”：",[79,4719,4721],{"className":81,"code":4720,"language":83,"meta":84,"style":84},"const HEATMAP_TEST_POINT_REFERENCE = Object.freeze({\n  longitude: 114.059,\n  latitude: 22.543,\n  name: '热力图测试点'\n})\n",[41,4722,4723,4744,4756,4768,4782],{"__ignoreMap":84},[88,4724,4725,4727,4730,4732,4735,4737,4740,4742],{"class":90,"line":91},[88,4726,264],{"class":263},[88,4728,4729],{"class":94}," HEATMAP_TEST_POINT_REFERENCE ",[88,4731,270],{"class":98},[88,4733,4734],{"class":94}," Object",[88,4736,99],{"class":98},[88,4738,4739],{"class":102},"freeze",[88,4741,130],{"class":94},[88,4743,617],{"class":98},[88,4745,4746,4749,4751,4754],{"class":90,"line":143},[88,4747,4748],{"class":631},"  longitude",[88,4750,1160],{"class":98},[88,4752,4753],{"class":634}," 114.059",[88,4755,1166],{"class":98},[88,4757,4758,4761,4763,4766],{"class":90,"line":180},[88,4759,4760],{"class":631},"  latitude",[88,4762,1160],{"class":98},[88,4764,4765],{"class":634}," 22.543",[88,4767,1166],{"class":98},[88,4769,4770,4773,4775,4777,4780],{"class":90,"line":332},[88,4771,4772],{"class":631},"  name",[88,4774,1160],{"class":98},[88,4776,112],{"class":98},[88,4778,4779],{"class":115},"热力图测试点",[88,4781,1720],{"class":98},[88,4783,4784,4786],{"class":90,"line":504},[88,4785,687],{"class":98},[88,4787,140],{"class":94},[31,4789,4790],{},"然后从当前热力图点集中挑选最适合观察的点：",[79,4792,4794],{"className":81,"code":4793,"language":83,"meta":84,"style":84},"function findHeatmapTestPoint(featureCollection) {\n  const features = Array.isArray(featureCollection?.features) ? featureCollection.features : []\n  let bestPoint = null\n  let bestScore = Number.POSITIVE_INFINITY\n\n  for (const feature of features) {\n    const point = getFeaturePoint(feature)\n\n    if (!point) {\n      continue\n    }\n\n    const longitudeDelta = point.longitude - HEATMAP_TEST_POINT_REFERENCE.longitude\n    const latitudeDelta = point.latitude - HEATMAP_TEST_POINT_REFERENCE.latitude\n    const distanceScore = longitudeDelta * longitudeDelta + latitudeDelta * latitudeDelta\n    const valueBonus = point.value \u002F 100000\n    const score = distanceScore - valueBonus\n",[41,4795,4796,4812,4854,4866,4883,4887,4907,4925,4929,4944,4949,4953,4957,4982,5006,5032,5053],{"__ignoreMap":84},[88,4797,4798,4800,4803,4805,4808,4810],{"class":90,"line":91},[88,4799,2482],{"class":263},[88,4801,4802],{"class":102}," findHeatmapTestPoint",[88,4804,130],{"class":98},[88,4806,4807],{"class":2364},"featureCollection",[88,4809,122],{"class":98},[88,4811,1152],{"class":98},[88,4813,4814,4816,4819,4821,4824,4826,4828,4830,4832,4834,4836,4838,4841,4844,4846,4848,4851],{"class":90,"line":143},[88,4815,4001],{"class":263},[88,4817,4818],{"class":94}," features",[88,4820,690],{"class":98},[88,4822,4823],{"class":94}," Array",[88,4825,99],{"class":98},[88,4827,3042],{"class":102},[88,4829,130],{"class":631},[88,4831,4807],{"class":94},[88,4833,569],{"class":98},[88,4835,2748],{"class":94},[88,4837,2326],{"class":631},[88,4839,4840],{"class":98},"?",[88,4842,4843],{"class":94}," featureCollection",[88,4845,99],{"class":98},[88,4847,2748],{"class":94},[88,4849,4850],{"class":98}," :",[88,4852,4853],{"class":631}," []\n",[88,4855,4856,4859,4862,4864],{"class":90,"line":180},[88,4857,4858],{"class":263},"  let",[88,4860,4861],{"class":94}," bestPoint",[88,4863,690],{"class":98},[88,4865,2260],{"class":98},[88,4867,4868,4870,4873,4875,4878,4880],{"class":90,"line":332},[88,4869,4858],{"class":263},[88,4871,4872],{"class":94}," bestScore",[88,4874,690],{"class":98},[88,4876,4877],{"class":94}," Number",[88,4879,99],{"class":98},[88,4881,4882],{"class":631},"POSITIVE_INFINITY\n",[88,4884,4885],{"class":90,"line":504},[88,4886,291],{"emptyLinePlaceholder":10},[88,4888,4889,4892,4894,4896,4898,4901,4903,4905],{"class":90,"line":509},[88,4890,4891],{"class":296},"  for",[88,4893,1578],{"class":631},[88,4895,264],{"class":263},[88,4897,2996],{"class":94},[88,4899,4900],{"class":98}," of",[88,4902,4818],{"class":94},[88,4904,2326],{"class":631},[88,4906,617],{"class":98},[88,4908,4909,4911,4913,4915,4918,4920,4923],{"class":90,"line":1325},[88,4910,2611],{"class":263},[88,4912,3347],{"class":94},[88,4914,690],{"class":98},[88,4916,4917],{"class":102}," getFeaturePoint",[88,4919,130],{"class":631},[88,4921,4922],{"class":94},"feature",[88,4924,140],{"class":631},[88,4926,4927],{"class":90,"line":1345},[88,4928,291],{"emptyLinePlaceholder":10},[88,4930,4931,4933,4935,4937,4940,4942],{"class":90,"line":1357},[88,4932,2629],{"class":296},[88,4934,1578],{"class":631},[88,4936,1581],{"class":98},[88,4938,4939],{"class":94},"point",[88,4941,2326],{"class":631},[88,4943,617],{"class":98},[88,4945,4946],{"class":90,"line":1376},[88,4947,4948],{"class":296},"      continue\n",[88,4950,4951],{"class":90,"line":1382},[88,4952,2661],{"class":98},[88,4954,4955],{"class":90,"line":1846},[88,4956,291],{"emptyLinePlaceholder":10},[88,4958,4959,4961,4964,4966,4968,4970,4972,4974,4977,4979],{"class":90,"line":1859},[88,4960,2611],{"class":263},[88,4962,4963],{"class":94}," longitudeDelta",[88,4965,690],{"class":98},[88,4967,3347],{"class":94},[88,4969,99],{"class":98},[88,4971,401],{"class":94},[88,4973,1074],{"class":98},[88,4975,4976],{"class":94}," HEATMAP_TEST_POINT_REFERENCE",[88,4978,99],{"class":98},[88,4980,4981],{"class":94},"longitude\n",[88,4983,4984,4986,4989,4991,4993,4995,4997,4999,5001,5003],{"class":90,"line":1870},[88,4985,2611],{"class":263},[88,4987,4988],{"class":94}," latitudeDelta",[88,4990,690],{"class":98},[88,4992,3347],{"class":94},[88,4994,99],{"class":98},[88,4996,463],{"class":94},[88,4998,1074],{"class":98},[88,5000,4976],{"class":94},[88,5002,99],{"class":98},[88,5004,5005],{"class":94},"latitude\n",[88,5007,5008,5010,5013,5015,5017,5020,5022,5025,5027,5029],{"class":90,"line":1876},[88,5009,2611],{"class":263},[88,5011,5012],{"class":94}," distanceScore",[88,5014,690],{"class":98},[88,5016,4963],{"class":94},[88,5018,5019],{"class":98}," *",[88,5021,4963],{"class":94},[88,5023,5024],{"class":98}," +",[88,5026,4988],{"class":94},[88,5028,5019],{"class":98},[88,5030,5031],{"class":94}," latitudeDelta\n",[88,5033,5034,5036,5039,5041,5043,5045,5047,5050],{"class":90,"line":1893},[88,5035,2611],{"class":263},[88,5037,5038],{"class":94}," valueBonus",[88,5040,690],{"class":98},[88,5042,3347],{"class":94},[88,5044,99],{"class":98},[88,5046,1886],{"class":94},[88,5048,5049],{"class":98}," \u002F",[88,5051,5052],{"class":634}," 100000\n",[88,5054,5055,5057,5060,5062,5064,5066],{"class":90,"line":1906},[88,5056,2611],{"class":263},[88,5058,5059],{"class":94}," score",[88,5061,690],{"class":98},[88,5063,5012],{"class":94},[88,5065,1074],{"class":98},[88,5067,5068],{"class":94}," valueBonus\n",[31,5070,5071],{},"这个策略的目的不是找“全局最大值”，而是找“离城市高密区域更近、同时又确实属于当前热力图数据”的一个真实点。这样相机切过去时，更容易直接看到 OSM Buildings，也更容易观察热力图是否贴到了建筑表面。",[27,5073,5074],{"id":5074},"测试点怎么可视化和定位",[31,5076,5077],{},"选出测试点后，Demo 会往场景里加一个贴地 marker：",[79,5079,5081],{"className":81,"code":5080,"language":83,"meta":84,"style":84},"return viewer.entities.add({\n  position: Cesium.Cartesian3.fromDegrees(testPoint.longitude, testPoint.latitude, 0),\n  point: {\n    pixelSize: 12,\n    color: Cesium.Color.fromCssColorString('#fff173'),\n    outlineColor: Cesium.Color.fromCssColorString('#08111f'),\n    outlineWidth: 2,\n    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND\n  },\n  label: {\n    text: labelText,\n    showBackground: true,\n    heightReference: Cesium.HeightReference.CLAMP_TO_GROUND\n  }\n})\n",[41,5082,5083,5102,5144,5153,5165,5194,5224,5235,5254,5258,5267,5279,5290,5306,5310],{"__ignoreMap":84},[88,5084,5085,5087,5089,5091,5094,5096,5098,5100],{"class":90,"line":91},[88,5086,297],{"class":296},[88,5088,273],{"class":94},[88,5090,99],{"class":98},[88,5092,5093],{"class":94},"entities",[88,5095,99],{"class":98},[88,5097,103],{"class":102},[88,5099,130],{"class":94},[88,5101,617],{"class":98},[88,5103,5104,5107,5109,5111,5113,5115,5117,5120,5123,5125,5127,5129,5132,5134,5136,5138,5140,5142],{"class":90,"line":143},[88,5105,5106],{"class":631},"  position",[88,5108,1160],{"class":98},[88,5110,517],{"class":94},[88,5112,99],{"class":98},[88,5114,909],{"class":94},[88,5116,99],{"class":98},[88,5118,5119],{"class":102},"fromDegrees",[88,5121,5122],{"class":94},"(testPoint",[88,5124,99],{"class":98},[88,5126,401],{"class":94},[88,5128,109],{"class":98},[88,5130,5131],{"class":94}," testPoint",[88,5133,99],{"class":98},[88,5135,463],{"class":94},[88,5137,109],{"class":98},[88,5139,883],{"class":634},[88,5141,122],{"class":94},[88,5143,1166],{"class":98},[88,5145,5146,5149,5151],{"class":90,"line":180},[88,5147,5148],{"class":631},"  point",[88,5150,1160],{"class":98},[88,5152,1152],{"class":98},[88,5154,5155,5158,5160,5163],{"class":90,"line":332},[88,5156,5157],{"class":631},"    pixelSize",[88,5159,1160],{"class":98},[88,5161,5162],{"class":634}," 12",[88,5164,1166],{"class":98},[88,5166,5167,5169,5171,5173,5175,5177,5179,5181,5183,5185,5188,5190,5192],{"class":90,"line":504},[88,5168,3945],{"class":631},[88,5170,1160],{"class":98},[88,5172,517],{"class":94},[88,5174,99],{"class":98},[88,5176,1209],{"class":94},[88,5178,99],{"class":98},[88,5180,4484],{"class":102},[88,5182,130],{"class":94},[88,5184,119],{"class":98},[88,5186,5187],{"class":115},"#fff173",[88,5189,119],{"class":98},[88,5191,122],{"class":94},[88,5193,1166],{"class":98},[88,5195,5196,5199,5201,5203,5205,5207,5209,5211,5213,5215,5218,5220,5222],{"class":90,"line":509},[88,5197,5198],{"class":631},"    outlineColor",[88,5200,1160],{"class":98},[88,5202,517],{"class":94},[88,5204,99],{"class":98},[88,5206,1209],{"class":94},[88,5208,99],{"class":98},[88,5210,4484],{"class":102},[88,5212,130],{"class":94},[88,5214,119],{"class":98},[88,5216,5217],{"class":115},"#08111f",[88,5219,119],{"class":98},[88,5221,122],{"class":94},[88,5223,1166],{"class":98},[88,5225,5226,5229,5231,5233],{"class":90,"line":1325},[88,5227,5228],{"class":631},"    outlineWidth",[88,5230,1160],{"class":98},[88,5232,1027],{"class":634},[88,5234,1166],{"class":98},[88,5236,5237,5240,5242,5244,5246,5249,5251],{"class":90,"line":1345},[88,5238,5239],{"class":631},"    heightReference",[88,5241,1160],{"class":98},[88,5243,517],{"class":94},[88,5245,99],{"class":98},[88,5247,5248],{"class":94},"HeightReference",[88,5250,99],{"class":98},[88,5252,5253],{"class":94},"CLAMP_TO_GROUND\n",[88,5255,5256],{"class":90,"line":1357},[88,5257,1873],{"class":98},[88,5259,5260,5263,5265],{"class":90,"line":1376},[88,5261,5262],{"class":631},"  label",[88,5264,1160],{"class":98},[88,5266,1152],{"class":98},[88,5268,5269,5272,5274,5277],{"class":90,"line":1382},[88,5270,5271],{"class":631},"    text",[88,5273,1160],{"class":98},[88,5275,5276],{"class":94}," labelText",[88,5278,1166],{"class":98},[88,5280,5281,5284,5286,5288],{"class":90,"line":1846},[88,5282,5283],{"class":631},"    showBackground",[88,5285,1160],{"class":98},[88,5287,1311],{"class":767},[88,5289,1166],{"class":98},[88,5291,5292,5294,5296,5298,5300,5302,5304],{"class":90,"line":1859},[88,5293,5239],{"class":631},[88,5295,1160],{"class":98},[88,5297,517],{"class":94},[88,5299,99],{"class":98},[88,5301,5248],{"class":94},[88,5303,99],{"class":98},[88,5305,5253],{"class":94},[88,5307,5308],{"class":90,"line":1870},[88,5309,1379],{"class":98},[88,5311,5312,5314],{"class":90,"line":1876},[88,5313,687],{"class":98},[88,5315,140],{"class":94},[31,5317,5318],{},"同时提供一个专门的相机动作：",[79,5320,5322],{"className":81,"code":5321,"language":83,"meta":84,"style":84},"function focusTestPoint(Cesium, viewer, testPoint, duration = 1.2) {\n  viewer.camera.flyTo({\n    destination: Cesium.Cartesian3.fromDegrees(testPoint.longitude, testPoint.latitude, 1800),\n    orientation: {\n      heading: Cesium.Math.toRadians(-18),\n      pitch: Cesium.Math.toRadians(-38),\n      roll: 0\n    },\n    duration\n  })\n}\n",[41,5323,5324,5357,5374,5417,5426,5456,5484,5494,5499,5504,5510],{"__ignoreMap":84},[88,5325,5326,5328,5331,5333,5335,5337,5339,5341,5343,5345,5348,5350,5353,5355],{"class":90,"line":91},[88,5327,2482],{"class":263},[88,5329,5330],{"class":102}," focusTestPoint",[88,5332,130],{"class":98},[88,5334,1987],{"class":2364},[88,5336,109],{"class":98},[88,5338,273],{"class":2364},[88,5340,109],{"class":98},[88,5342,5131],{"class":2364},[88,5344,109],{"class":98},[88,5346,5347],{"class":2364}," duration",[88,5349,690],{"class":98},[88,5351,5352],{"class":634}," 1.2",[88,5354,122],{"class":98},[88,5356,1152],{"class":98},[88,5358,5359,5361,5363,5365,5367,5370,5372],{"class":90,"line":143},[88,5360,335],{"class":94},[88,5362,99],{"class":98},[88,5364,278],{"class":94},[88,5366,99],{"class":98},[88,5368,5369],{"class":102},"flyTo",[88,5371,130],{"class":631},[88,5373,617],{"class":98},[88,5375,5376,5379,5381,5383,5385,5387,5389,5391,5393,5396,5398,5400,5402,5404,5406,5408,5410,5413,5415],{"class":90,"line":180},[88,5377,5378],{"class":631},"    destination",[88,5380,1160],{"class":98},[88,5382,517],{"class":94},[88,5384,99],{"class":98},[88,5386,909],{"class":94},[88,5388,99],{"class":98},[88,5390,5119],{"class":102},[88,5392,130],{"class":631},[88,5394,5395],{"class":94},"testPoint",[88,5397,99],{"class":98},[88,5399,401],{"class":94},[88,5401,109],{"class":98},[88,5403,5131],{"class":94},[88,5405,99],{"class":98},[88,5407,463],{"class":94},[88,5409,109],{"class":98},[88,5411,5412],{"class":634}," 1800",[88,5414,122],{"class":631},[88,5416,1166],{"class":98},[88,5418,5419,5422,5424],{"class":90,"line":332},[88,5420,5421],{"class":631},"    orientation",[88,5423,1160],{"class":98},[88,5425,1152],{"class":98},[88,5427,5428,5431,5433,5435,5437,5440,5442,5445,5447,5449,5452,5454],{"class":90,"line":504},[88,5429,5430],{"class":631},"      heading",[88,5432,1160],{"class":98},[88,5434,517],{"class":94},[88,5436,99],{"class":98},[88,5438,5439],{"class":94},"Math",[88,5441,99],{"class":98},[88,5443,5444],{"class":102},"toRadians",[88,5446,130],{"class":631},[88,5448,3213],{"class":98},[88,5450,5451],{"class":634},"18",[88,5453,122],{"class":631},[88,5455,1166],{"class":98},[88,5457,5458,5461,5463,5465,5467,5469,5471,5473,5475,5477,5480,5482],{"class":90,"line":509},[88,5459,5460],{"class":631},"      pitch",[88,5462,1160],{"class":98},[88,5464,517],{"class":94},[88,5466,99],{"class":98},[88,5468,5439],{"class":94},[88,5470,99],{"class":98},[88,5472,5444],{"class":102},[88,5474,130],{"class":631},[88,5476,3213],{"class":98},[88,5478,5479],{"class":634},"38",[88,5481,122],{"class":631},[88,5483,1166],{"class":98},[88,5485,5486,5489,5491],{"class":90,"line":1325},[88,5487,5488],{"class":631},"      roll",[88,5490,1160],{"class":98},[88,5492,5493],{"class":634}," 0\n",[88,5495,5496],{"class":90,"line":1345},[88,5497,5498],{"class":98},"    },\n",[88,5500,5501],{"class":90,"line":1357},[88,5502,5503],{"class":94},"    duration\n",[88,5505,5506,5508],{"class":90,"line":1376},[88,5507,2685],{"class":98},[88,5509,140],{"class":631},[88,5511,5512],{"class":90,"line":1382},[88,5513,657],{"class":98},[31,5515,5516],{},"这样一来：",[35,5518,5519,5522],{},[38,5520,5521],{},"俯视时可以看热力图贴地范围",[38,5523,5524],{},"近看时可以看热力图是否爬到了 OSM Buildings 表面",[27,5526,5528],{"id":5527},"classificationtarget-在测试场景里的作用","classificationTarget 在测试场景里的作用",[31,5530,5531,5532,5534],{},"当测试环境已经把地形和 OSM Buildings 都加载进来以后，",[41,5533,2200],{}," 的行为会更直观：",[35,5536,5537,5543,5549],{},[38,5538,5539,5542],{},[41,5540,5541],{},"'terrain'","：只看热力图贴地",[38,5544,5545,5548],{},[41,5546,5547],{},"'tiles'","：只看热力图贴模型",[38,5550,5551,5554],{},[41,5552,5553],{},"'both'","：同时看贴地和贴模型",[31,5556,5557,5558,5560],{},"这也是为什么这次测试场景要同时把 terrain 和 tiles 都拉起来。否则即使 SDK 层支持 ",[41,5559,5547],{},"，页面里也没有可观察的模型目标。",[27,5562,5564],{"id":5563},"classificationtarget-怎么控制地形和-3d-tiles","classificationTarget 怎么控制地形和 3D Tiles",[31,5566,5567,5568,5570],{},"旧方案需要额外把热力图纹理再绑定到 tileset，现在不需要了。显示目标统一用 ",[41,5569,2200],{}," 控制：",[79,5572,5574],{"className":81,"code":5573,"language":83,"meta":84,"style":84},"function toClassificationType(Cesium, target) {\n  const normalizedTarget = normalizeClassificationTarget(target)\n\n  if (normalizedTarget === 'terrain') {\n    return Cesium.ClassificationType.TERRAIN\n  }\n\n  if (normalizedTarget === 'tiles') {\n    return Cesium.ClassificationType.CESIUM_3D_TILE\n  }\n\n  return Cesium.ClassificationType.BOTH\n}\n",[41,5575,5576,5595,5614,5618,5640,5656,5660,5664,5684,5699,5703,5707,5722],{"__ignoreMap":84},[88,5577,5578,5580,5582,5584,5586,5588,5591,5593],{"class":90,"line":91},[88,5579,2482],{"class":263},[88,5581,4236],{"class":102},[88,5583,130],{"class":98},[88,5585,1987],{"class":2364},[88,5587,109],{"class":98},[88,5589,5590],{"class":2364}," target",[88,5592,122],{"class":98},[88,5594,1152],{"class":98},[88,5596,5597,5599,5602,5604,5607,5609,5612],{"class":90,"line":143},[88,5598,4001],{"class":263},[88,5600,5601],{"class":94}," normalizedTarget",[88,5603,690],{"class":98},[88,5605,5606],{"class":102}," normalizeClassificationTarget",[88,5608,130],{"class":631},[88,5610,5611],{"class":94},"target",[88,5613,140],{"class":631},[88,5615,5616],{"class":90,"line":180},[88,5617,291],{"emptyLinePlaceholder":10},[88,5619,5620,5622,5624,5627,5629,5631,5634,5636,5638],{"class":90,"line":332},[88,5621,2316],{"class":296},[88,5623,1578],{"class":631},[88,5625,5626],{"class":94},"normalizedTarget",[88,5628,2504],{"class":98},[88,5630,112],{"class":98},[88,5632,5633],{"class":115},"terrain",[88,5635,119],{"class":98},[88,5637,2326],{"class":631},[88,5639,617],{"class":98},[88,5641,5642,5644,5646,5648,5651,5653],{"class":90,"line":504},[88,5643,2520],{"class":296},[88,5645,517],{"class":94},[88,5647,99],{"class":98},[88,5649,5650],{"class":94},"ClassificationType",[88,5652,99],{"class":98},[88,5654,5655],{"class":94},"TERRAIN\n",[88,5657,5658],{"class":90,"line":509},[88,5659,1379],{"class":98},[88,5661,5662],{"class":90,"line":1325},[88,5663,291],{"emptyLinePlaceholder":10},[88,5665,5666,5668,5670,5672,5674,5676,5678,5680,5682],{"class":90,"line":1345},[88,5667,2316],{"class":296},[88,5669,1578],{"class":631},[88,5671,5626],{"class":94},[88,5673,2504],{"class":98},[88,5675,112],{"class":98},[88,5677,2098],{"class":115},[88,5679,119],{"class":98},[88,5681,2326],{"class":631},[88,5683,617],{"class":98},[88,5685,5686,5688,5690,5692,5694,5696],{"class":90,"line":1357},[88,5687,2520],{"class":296},[88,5689,517],{"class":94},[88,5691,99],{"class":98},[88,5693,5650],{"class":94},[88,5695,99],{"class":98},[88,5697,5698],{"class":94},"CESIUM_3D_TILE\n",[88,5700,5701],{"class":90,"line":1376},[88,5702,1379],{"class":98},[88,5704,5705],{"class":90,"line":1382},[88,5706,291],{"emptyLinePlaceholder":10},[88,5708,5709,5711,5713,5715,5717,5719],{"class":90,"line":1846},[88,5710,622],{"class":296},[88,5712,517],{"class":94},[88,5714,99],{"class":98},[88,5716,5650],{"class":94},[88,5718,99],{"class":98},[88,5720,5721],{"class":94},"BOTH\n",[88,5723,5724],{"class":90,"line":1859},[88,5725,657],{"class":98},[31,5727,5728],{},"这意味着：",[35,5730,5731,5736,5741],{},[38,5732,5733,5735],{},[41,5734,5541],{}," 只作用到地形",[38,5737,5738,5740],{},[41,5739,5547],{}," 只作用到 3D Tiles",[38,5742,5743,5745],{},[41,5744,5553],{}," 同时作用到两者",[31,5747,5748],{},"SDK 对外不暴露 Cesium 原生枚举，业务只需要记住三个字符串即可。",[27,5750,5751],{"id":5751},"参数更新为什么能实时生效",[31,5753,5754],{},"半径、模糊度、透明度变化时，不需要重建 Viewer，也不需要重新创建 primitive。SDK 只需要重绘热力纹理，然后把新 canvas 填回材质：",[79,5756,5758],{"className":81,"code":5757,"language":83,"meta":84,"style":84},"function syncGroundMaterial() {\n  if (!state.groundMaterial) {\n    rebuildGroundPrimitive()\n    return\n  }\n\n  state.groundMaterial.uniforms.image = state.renderResult.canvas\n}\n",[41,5759,5760,5771,5791,5798,5803,5807,5811,5843],{"__ignoreMap":84},[88,5761,5762,5764,5767,5769],{"class":90,"line":91},[88,5763,2482],{"class":263},[88,5765,5766],{"class":102}," syncGroundMaterial",[88,5768,2291],{"class":98},[88,5770,1152],{"class":98},[88,5772,5773,5775,5777,5779,5782,5784,5787,5789],{"class":90,"line":143},[88,5774,2316],{"class":296},[88,5776,1578],{"class":631},[88,5778,1581],{"class":98},[88,5780,5781],{"class":94},"state",[88,5783,99],{"class":98},[88,5785,5786],{"class":94},"groundMaterial",[88,5788,2326],{"class":631},[88,5790,617],{"class":98},[88,5792,5793,5796],{"class":90,"line":180},[88,5794,5795],{"class":102},"    rebuildGroundPrimitive",[88,5797,1498],{"class":631},[88,5799,5800],{"class":90,"line":332},[88,5801,5802],{"class":296},"    return\n",[88,5804,5805],{"class":90,"line":504},[88,5806,1379],{"class":98},[88,5808,5809],{"class":90,"line":509},[88,5810,291],{"emptyLinePlaceholder":10},[88,5812,5813,5816,5818,5820,5822,5825,5827,5830,5832,5834,5836,5838,5840],{"class":90,"line":1325},[88,5814,5815],{"class":94},"  state",[88,5817,99],{"class":98},[88,5819,5786],{"class":94},[88,5821,99],{"class":98},[88,5823,5824],{"class":94},"uniforms",[88,5826,99],{"class":98},[88,5828,5829],{"class":94},"image",[88,5831,690],{"class":98},[88,5833,3992],{"class":94},[88,5835,99],{"class":98},[88,5837,4021],{"class":94},[88,5839,99],{"class":98},[88,5841,5842],{"class":94},"canvas\n",[88,5844,5845],{"class":90,"line":1345},[88,5846,657],{"class":98},[31,5848,5849],{},"只有两个场景需要重建 primitive：",[35,5851,5852,5857],{},[38,5853,5854,5856],{},[41,5855,1994],{}," 变了",[38,5858,5859,5856],{},[41,5860,2200],{},[31,5862,5863,5864,371],{},"对应的重建入口集中在 ",[41,5865,5866],{},"rebuildHeatmap()",[79,5868,5870],{"className":81,"code":5869,"language":83,"meta":84,"style":84},"function rebuildHeatmap({ rebuildPrimitive = false } = {}) {\n  if (state.destroyed) {\n    return\n  }\n\n  state.renderResult = renderHeatmapCanvas(state.parsed.points, state.bounds, state.heatmapOptions)\n\n  if (rebuildPrimitive || !state.groundPrimitive) {\n    rebuildGroundPrimitive()\n  }\n  else {\n    syncGroundMaterial()\n  }\n\n  requestSceneRender()\n}\n",[41,5871,5872,5898,5915,5919,5923,5927,5973,5977,6001,6007,6011,6018,6025,6029,6033,6040],{"__ignoreMap":84},[88,5873,5874,5876,5879,5882,5885,5887,5889,5891,5893,5896],{"class":90,"line":91},[88,5875,2482],{"class":263},[88,5877,5878],{"class":102}," rebuildHeatmap",[88,5880,5881],{"class":98},"({",[88,5883,5884],{"class":2364}," rebuildPrimitive",[88,5886,690],{"class":98},[88,5888,768],{"class":767},[88,5890,1709],{"class":98},[88,5892,690],{"class":98},[88,5894,5895],{"class":98}," {})",[88,5897,1152],{"class":98},[88,5899,5900,5902,5904,5906,5908,5911,5913],{"class":90,"line":143},[88,5901,2316],{"class":296},[88,5903,1578],{"class":631},[88,5905,5781],{"class":94},[88,5907,99],{"class":98},[88,5909,5910],{"class":94},"destroyed",[88,5912,2326],{"class":631},[88,5914,617],{"class":98},[88,5916,5917],{"class":90,"line":180},[88,5918,5802],{"class":296},[88,5920,5921],{"class":90,"line":332},[88,5922,1379],{"class":98},[88,5924,5925],{"class":90,"line":504},[88,5926,291],{"emptyLinePlaceholder":10},[88,5928,5929,5931,5933,5935,5937,5940,5942,5944,5946,5949,5951,5954,5956,5958,5960,5962,5964,5966,5968,5971],{"class":90,"line":509},[88,5930,5815],{"class":94},[88,5932,99],{"class":98},[88,5934,4021],{"class":94},[88,5936,690],{"class":98},[88,5938,5939],{"class":102}," renderHeatmapCanvas",[88,5941,130],{"class":631},[88,5943,5781],{"class":94},[88,5945,99],{"class":98},[88,5947,5948],{"class":94},"parsed",[88,5950,99],{"class":98},[88,5952,5953],{"class":94},"points",[88,5955,109],{"class":98},[88,5957,3992],{"class":94},[88,5959,99],{"class":98},[88,5961,1994],{"class":94},[88,5963,109],{"class":98},[88,5965,3992],{"class":94},[88,5967,99],{"class":98},[88,5969,5970],{"class":94},"heatmapOptions",[88,5972,140],{"class":631},[88,5974,5975],{"class":90,"line":1325},[88,5976,291],{"emptyLinePlaceholder":10},[88,5978,5979,5981,5983,5986,5988,5990,5992,5994,5997,5999],{"class":90,"line":1345},[88,5980,2316],{"class":296},[88,5982,1578],{"class":631},[88,5984,5985],{"class":94},"rebuildPrimitive",[88,5987,2560],{"class":98},[88,5989,3085],{"class":98},[88,5991,5781],{"class":94},[88,5993,99],{"class":98},[88,5995,5996],{"class":94},"groundPrimitive",[88,5998,2326],{"class":631},[88,6000,617],{"class":98},[88,6002,6003,6005],{"class":90,"line":1357},[88,6004,5795],{"class":102},[88,6006,1498],{"class":631},[88,6008,6009],{"class":90,"line":1376},[88,6010,1379],{"class":98},[88,6012,6013,6016],{"class":90,"line":1382},[88,6014,6015],{"class":296},"  else",[88,6017,1152],{"class":98},[88,6019,6020,6023],{"class":90,"line":1846},[88,6021,6022],{"class":102},"    syncGroundMaterial",[88,6024,1498],{"class":631},[88,6026,6027],{"class":90,"line":1859},[88,6028,1379],{"class":98},[88,6030,6031],{"class":90,"line":1870},[88,6032,291],{"emptyLinePlaceholder":10},[88,6034,6035,6038],{"class":90,"line":1876},[88,6036,6037],{"class":102},"  requestSceneRender",[88,6039,1498],{"class":631},[88,6041,6042],{"class":90,"line":1893},[88,6043,657],{"class":98},[31,6045,6046],{},"这就是这次改造后的关键性能边界：普通调参不重建几何，只有几何范围和分类目标变化时才重新创建 primitive。",[27,6048,6050],{"id":6049},"geojson-更新怎么处理","GeoJSON 更新怎么处理",[31,6052,6053,6054,371],{},"业务数据更新时，统一调用 ",[41,6055,6056],{},"updateGeoJson()",[79,6058,6060],{"className":81,"code":6059,"language":83,"meta":84,"style":84},"function updateGeoJson(featureCollection) {\n  state.geoJson = featureCollection || createEmptyFeatureCollection()\n  parseCurrentGeoJson()\n  rebuildHeatmap()\n\n  return getState()\n}\n",[41,6061,6062,6077,6097,6104,6111,6115,6124],{"__ignoreMap":84},[88,6063,6064,6066,6069,6071,6073,6075],{"class":90,"line":91},[88,6065,2482],{"class":263},[88,6067,6068],{"class":102}," updateGeoJson",[88,6070,130],{"class":98},[88,6072,4807],{"class":2364},[88,6074,122],{"class":98},[88,6076,1152],{"class":98},[88,6078,6079,6081,6083,6086,6088,6090,6092,6095],{"class":90,"line":143},[88,6080,5815],{"class":94},[88,6082,99],{"class":98},[88,6084,6085],{"class":94},"geoJson",[88,6087,690],{"class":98},[88,6089,4843],{"class":94},[88,6091,2560],{"class":98},[88,6093,6094],{"class":102}," createEmptyFeatureCollection",[88,6096,1498],{"class":631},[88,6098,6099,6102],{"class":90,"line":180},[88,6100,6101],{"class":102},"  parseCurrentGeoJson",[88,6103,1498],{"class":631},[88,6105,6106,6109],{"class":90,"line":332},[88,6107,6108],{"class":102},"  rebuildHeatmap",[88,6110,1498],{"class":631},[88,6112,6113],{"class":90,"line":504},[88,6114,291],{"emptyLinePlaceholder":10},[88,6116,6117,6119,6122],{"class":90,"line":509},[88,6118,622],{"class":296},[88,6120,6121],{"class":102}," getState",[88,6123,1498],{"class":631},[88,6125,6126],{"class":90,"line":1325},[88,6127,657],{"class":98},[31,6129,6130],{},"它会完整执行：",[35,6132,6133,6136,6139,6142,6145],{},[38,6134,6135],{},"替换 GeoJSON",[38,6137,6138],{},"重新校验点位",[38,6140,6141],{},"重新统计 accepted\u002Fskipped\u002FminValue\u002FmaxValue",[38,6143,6144],{},"重新绘制 canvas",[38,6146,6147],{},"同步更新 ground material",[31,6149,6150],{},"这样业务侧只表达“数据变了”，热力图图层内部负责决定需要刷新哪些资源。",[31,6152,6153,6154,6157],{},"Demo 在 ",[41,6155,6156],{},"regenerate()"," 里除了更新热力图本体，还会同步刷新测试点 marker，这样重新生成随机点位之后，观察锚点也会继续跟着当前热力图数据走。",[27,6159,6160],{"id":6160},"生命周期怎么清理",[31,6162,6163,6164,371],{},"Cesium 页面最容易遗留的是 primitive 和材质状态。SDK 里把这些清理集中到 ",[41,6165,6166],{},"destroy()",[79,6168,6170],{"className":81,"code":6169,"language":83,"meta":84,"style":84},"function destroyGroundPrimitive() {\n  if (!state.groundPrimitive) {\n    return\n  }\n\n  if (viewer.scene.primitives.contains(state.groundPrimitive)) {\n    viewer.scene.primitives.remove(state.groundPrimitive)\n  }\n\n  state.groundPrimitive = null\n  state.groundAppearance = null\n  state.groundMaterial = null\n}\n\nfunction destroy() {\n  state.destroyed = true\n  destroyGroundPrimitive()\n  requestSceneRender()\n}\n",[41,6171,6172,6183,6201,6205,6209,6213,6246,6274,6278,6282,6294,6307,6319,6323,6327,6338,6350,6357,6363],{"__ignoreMap":84},[88,6173,6174,6176,6179,6181],{"class":90,"line":91},[88,6175,2482],{"class":263},[88,6177,6178],{"class":102}," destroyGroundPrimitive",[88,6180,2291],{"class":98},[88,6182,1152],{"class":98},[88,6184,6185,6187,6189,6191,6193,6195,6197,6199],{"class":90,"line":143},[88,6186,2316],{"class":296},[88,6188,1578],{"class":631},[88,6190,1581],{"class":98},[88,6192,5781],{"class":94},[88,6194,99],{"class":98},[88,6196,5996],{"class":94},[88,6198,2326],{"class":631},[88,6200,617],{"class":98},[88,6202,6203],{"class":90,"line":180},[88,6204,5802],{"class":296},[88,6206,6207],{"class":90,"line":332},[88,6208,1379],{"class":98},[88,6210,6211],{"class":90,"line":504},[88,6212,291],{"emptyLinePlaceholder":10},[88,6214,6215,6217,6219,6221,6223,6225,6227,6229,6231,6234,6236,6238,6240,6242,6244],{"class":90,"line":509},[88,6216,2316],{"class":296},[88,6218,1578],{"class":631},[88,6220,1404],{"class":94},[88,6222,99],{"class":98},[88,6224,304],{"class":94},[88,6226,99],{"class":98},[88,6228,4668],{"class":94},[88,6230,99],{"class":98},[88,6232,6233],{"class":102},"contains",[88,6235,130],{"class":631},[88,6237,5781],{"class":94},[88,6239,99],{"class":98},[88,6241,5996],{"class":94},[88,6243,3749],{"class":631},[88,6245,617],{"class":98},[88,6247,6248,6251,6253,6255,6257,6259,6261,6264,6266,6268,6270,6272],{"class":90,"line":1325},[88,6249,6250],{"class":94},"    viewer",[88,6252,99],{"class":98},[88,6254,304],{"class":94},[88,6256,99],{"class":98},[88,6258,4668],{"class":94},[88,6260,99],{"class":98},[88,6262,6263],{"class":102},"remove",[88,6265,130],{"class":631},[88,6267,5781],{"class":94},[88,6269,99],{"class":98},[88,6271,5996],{"class":94},[88,6273,140],{"class":631},[88,6275,6276],{"class":90,"line":1345},[88,6277,1379],{"class":98},[88,6279,6280],{"class":90,"line":1357},[88,6281,291],{"emptyLinePlaceholder":10},[88,6283,6284,6286,6288,6290,6292],{"class":90,"line":1376},[88,6285,5815],{"class":94},[88,6287,99],{"class":98},[88,6289,5996],{"class":94},[88,6291,690],{"class":98},[88,6293,2260],{"class":98},[88,6295,6296,6298,6300,6303,6305],{"class":90,"line":1382},[88,6297,5815],{"class":94},[88,6299,99],{"class":98},[88,6301,6302],{"class":94},"groundAppearance",[88,6304,690],{"class":98},[88,6306,2260],{"class":98},[88,6308,6309,6311,6313,6315,6317],{"class":90,"line":1846},[88,6310,5815],{"class":94},[88,6312,99],{"class":98},[88,6314,5786],{"class":94},[88,6316,690],{"class":98},[88,6318,2260],{"class":98},[88,6320,6321],{"class":90,"line":1859},[88,6322,657],{"class":98},[88,6324,6325],{"class":90,"line":1870},[88,6326,291],{"emptyLinePlaceholder":10},[88,6328,6329,6331,6334,6336],{"class":90,"line":1876},[88,6330,2482],{"class":263},[88,6332,6333],{"class":102}," destroy",[88,6335,2291],{"class":98},[88,6337,1152],{"class":98},[88,6339,6340,6342,6344,6346,6348],{"class":90,"line":1893},[88,6341,5815],{"class":94},[88,6343,99],{"class":98},[88,6345,5910],{"class":94},[88,6347,690],{"class":98},[88,6349,1451],{"class":767},[88,6351,6352,6355],{"class":90,"line":1906},[88,6353,6354],{"class":102},"  destroyGroundPrimitive",[88,6356,1498],{"class":631},[88,6358,6359,6361],{"class":90,"line":1919},[88,6360,6037],{"class":102},[88,6362,1498],{"class":631},[88,6364,6365],{"class":90,"line":1932},[88,6366,657],{"class":98},[31,6368,6369],{},"这样路由切换或页面销毁时，业务只需要先销毁热力图图层，再销毁 Cesium Viewer。",[31,6371,6372],{},"对于 Demo 额外加进去的资源，也会在销毁时一并清理：",[79,6374,6376],{"className":81,"code":6375,"language":83,"meta":84,"style":84},"if (sceneState.tileset && viewer.scene.primitives.contains(sceneState.tileset)) {\n  viewer.scene.primitives.remove(sceneState.tileset)\n}\n\nif (sceneState.testMarker) {\n  viewer.entities.remove(sceneState.testMarker)\n}\n",[41,6377,6378,6417,6444,6448,6452,6465,6488],{"__ignoreMap":84},[88,6379,6380,6382,6385,6387,6390,6393,6395,6397,6399,6401,6403,6405,6407,6410,6412,6415],{"class":90,"line":91},[88,6381,603],{"class":296},[88,6383,6384],{"class":94}," (sceneState",[88,6386,99],{"class":98},[88,6388,6389],{"class":94},"tileset ",[88,6391,6392],{"class":98},"&&",[88,6394,273],{"class":94},[88,6396,99],{"class":98},[88,6398,304],{"class":94},[88,6400,99],{"class":98},[88,6402,4668],{"class":94},[88,6404,99],{"class":98},[88,6406,6233],{"class":102},[88,6408,6409],{"class":94},"(sceneState",[88,6411,99],{"class":98},[88,6413,6414],{"class":94},"tileset)) ",[88,6416,617],{"class":98},[88,6418,6419,6421,6423,6425,6427,6429,6431,6433,6435,6438,6440,6442],{"class":90,"line":143},[88,6420,335],{"class":94},[88,6422,99],{"class":98},[88,6424,304],{"class":94},[88,6426,99],{"class":98},[88,6428,4668],{"class":94},[88,6430,99],{"class":98},[88,6432,6263],{"class":102},[88,6434,130],{"class":631},[88,6436,6437],{"class":94},"sceneState",[88,6439,99],{"class":98},[88,6441,4655],{"class":94},[88,6443,140],{"class":631},[88,6445,6446],{"class":90,"line":180},[88,6447,657],{"class":98},[88,6449,6450],{"class":90,"line":332},[88,6451,291],{"emptyLinePlaceholder":10},[88,6453,6454,6456,6458,6460,6463],{"class":90,"line":504},[88,6455,603],{"class":296},[88,6457,6384],{"class":94},[88,6459,99],{"class":98},[88,6461,6462],{"class":94},"testMarker) ",[88,6464,617],{"class":98},[88,6466,6467,6469,6471,6473,6475,6477,6479,6481,6483,6486],{"class":90,"line":509},[88,6468,335],{"class":94},[88,6470,99],{"class":98},[88,6472,5093],{"class":94},[88,6474,99],{"class":98},[88,6476,6263],{"class":102},[88,6478,130],{"class":631},[88,6480,6437],{"class":94},[88,6482,99],{"class":98},[88,6484,6485],{"class":94},"testMarker",[88,6487,140],{"class":631},[88,6489,6490],{"class":90,"line":1325},[88,6491,657],{"class":98},[27,6493,6494],{"id":6494},"当前实现边界",[31,6496,6497],{},"当前实现是二维热力纹理，不是三维体渲染。它适合表达某个地理范围内的密度、强度或风险分布。",[31,6499,6500,6501,6503,6504,6506],{},"这次用 ",[41,6502,905],{}," 统一了地形和 3D Tiles 的显示目标控制，但 Cesium 官方也明确提示：textured ",[41,6505,905],{}," 更适合“模式化覆盖”，如果业务未来要追求特别严格的地表纹理精度，仍然要结合具体场景重新评估影像方案。",[31,6508,6509],{},"当前 Demo 的 3D Tiles 测试资源使用的是 OSM Buildings，它依赖在线资源和 Ion token。如果网络或 token 不可用，热力图本体仍然能继续工作，但“贴模型”这部分就没法在页面里直接观察到。",[31,6511,6512,6513,6515],{},"当前 ",[41,6514,1994],{}," 建议由业务侧显式传入。更进一步可以在 GeoJSON 解析阶段自动计算 bbox，再根据业务需要加 padding，这样热力图覆盖范围就能完全跟随数据本身。",[1620,6517,6518],{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}",{"title":84,"searchDepth":143,"depth":143,"links":6520},[6521,6522,6523,6524,6525,6526,6527,6528,6529,6530,6531,6532,6533,6534,6535,6536,6537],{"id":29,"depth":143,"text":29},{"id":1687,"depth":143,"text":1688},{"id":2158,"depth":143,"text":2158},{"id":2213,"depth":143,"text":2214},{"id":2693,"depth":143,"text":2694},{"id":2968,"depth":143,"text":2969},{"id":3171,"depth":143,"text":3172},{"id":3682,"depth":143,"text":3683},{"id":4344,"depth":143,"text":4345},{"id":4714,"depth":143,"text":4714},{"id":5074,"depth":143,"text":5074},{"id":5527,"depth":143,"text":5528},{"id":5563,"depth":143,"text":5564},{"id":5751,"depth":143,"text":5751},{"id":6049,"depth":143,"text":6050},{"id":6160,"depth":143,"text":6160},{"id":6494,"depth":143,"text":6494},"2026-04-23","记录通用 GeoJSON 热力图 SDK 的实现逻辑，包括 Cesium runtime 单例、GeoJSON 点数据解析、canvas 热力纹理生成、GroundPrimitive 分类渲染、世界地形接入和 OSM Buildings 测试场景管理。","\u002Fimages\u002Fdemos\u002Fcesium-geojson-heatmap.svg",{},"\u002Fblog\u002Fcesium-geojson-heatmap-sdk-implementation",{"title":1645,"description":6539},"blog\u002Fcesium-geojson-heatmap-sdk-implementation","sVDTnKGLZyWyWxzLWbOz6aqqrtvAslI-8wBcVqGWMf4",1777372185303]