|
|
@@ -0,0 +1,253 @@
|
|
|
+/*
|
|
|
+ * 无头渲染自动化钩子(外部注入版)——门户 Vue 页面保持零改动
|
|
|
+ * 由 task-service renderer.js 在页面加载后注入执行(page.evaluate(本文件源码))。
|
|
|
+ * 依赖页面全局:Cesium / viewer / scene;日照另需已挂载的 SunlightAnalysis 组件实例。
|
|
|
+ * 暴露:
|
|
|
+ * window.__sunlightRun({geojson, date, startTime, endTime}) -> Promise<dataURL>
|
|
|
+ * window.__skylineRun({start, end, faceH, faceW, slider}) -> Promise<dataURL>
|
|
|
+ */
|
|
|
+(function () {
|
|
|
+ // 在页面 DOM 里找 Vue2 组件实例(Vue2 会在组件根元素上挂 __vue__)
|
|
|
+ function findVueInstance(methods) {
|
|
|
+ var els = document.querySelectorAll("*");
|
|
|
+ for (var i = 0; i < els.length; i++) {
|
|
|
+ var vm = els[i].__vue__;
|
|
|
+ if (!vm) continue;
|
|
|
+ var ok = true;
|
|
|
+ for (var m = 0; m < methods.length; m++) {
|
|
|
+ if (typeof vm[methods[m]] !== "function") {
|
|
|
+ ok = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (ok) return vm;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ window.__findSunlightInstance = function () {
|
|
|
+ return findVueInstance(["statrSunlight", "exportResultDataUrl", "analysis"]);
|
|
|
+ };
|
|
|
+
|
|
|
+ function waitTilesLoaded(scene) {
|
|
|
+ return new Promise(function (resolve) {
|
|
|
+ var start = Date.now();
|
|
|
+ var timer = setInterval(function () {
|
|
|
+ var done = false;
|
|
|
+ try {
|
|
|
+ done = scene && scene.globe && scene.globe.tilesLoaded === true;
|
|
|
+ } catch (e) { }
|
|
|
+ if (done || Date.now() - start > 25000) {
|
|
|
+ clearInterval(timer);
|
|
|
+ setTimeout(resolve, 3000); // 额外等待,让高分辨率瓦片续流
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ window.__sunlightSetupRange = async function (geojson) {
|
|
|
+ // var that = window.__sunlightInstance;
|
|
|
+ var that = window.__findSunlightInstance();
|
|
|
+ var allCoords = geojson.geometry.coordinates[0];
|
|
|
+ // 去重首尾重复点(等同自定义绘制的去重顶点)
|
|
|
+ var coords = allCoords;
|
|
|
+ if (allCoords.length > 1) {
|
|
|
+ var fst = allCoords[0],
|
|
|
+ lst = allCoords[allCoords.length - 1];
|
|
|
+ if (fst[0] === lst[0] && fst[1] === lst[1]) {
|
|
|
+ coords = allCoords.slice(0, allCoords.length - 1);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // 采样 DSM 地形高程,生成带真实高程的 ECEF(z=地表高程才能把彩带渲染在地表上)
|
|
|
+ var cartos = [];
|
|
|
+ for (var si = 0; si < coords.length; si++) {
|
|
|
+ cartos.push(
|
|
|
+ Cesium.Cartographic.fromDegrees(coords[si][0], coords[si][1])
|
|
|
+ );
|
|
|
+ }
|
|
|
+ if (window.viewer.terrainProvider && window.viewer.terrainProvider.visible) {
|
|
|
+ try {
|
|
|
+ await Cesium.sampleTerrainMostDetailed(
|
|
|
+ window.viewer.terrainProvider,
|
|
|
+ cartos
|
|
|
+ );
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("sampleTerrain error", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var cart = [];
|
|
|
+ for (var sci = 0; sci < cartos.length; sci++) {
|
|
|
+ if (!cartos[sci]) continue; // 采样失败返回 undefined 的点跳过
|
|
|
+ cart.push(
|
|
|
+ Cesium.Cartesian3.fromDegrees(
|
|
|
+ Cesium.Math.toDegrees(cartos[sci].longitude),
|
|
|
+ Cesium.Math.toDegrees(cartos[sci].latitude),
|
|
|
+ cartos[sci].height
|
|
|
+ )
|
|
|
+ );
|
|
|
+ }
|
|
|
+ // 不创建多边形实体(避免 Cesium 多边形几何异步抛 DeveloperError),分析只依赖 positions + boxAll
|
|
|
+ that.clear();
|
|
|
+ that.ProjectScope.positions = JSON.parse(JSON.stringify(cart));
|
|
|
+ // boxAll + 飞到范围(ExportResult 也用 boxAll 出图)
|
|
|
+ var ring = [];
|
|
|
+ for (var ri = 0; ri < coords.length; ri++) {
|
|
|
+ ring.push([coords[ri][0], coords[ri][1]]);
|
|
|
+ }
|
|
|
+ // turf polygon() 要求闭环(首尾点相同),否则报 "First and last positions are not equivalent"
|
|
|
+ var closedRing = ring.slice();
|
|
|
+ if (
|
|
|
+ closedRing.length > 1 &&
|
|
|
+ (closedRing[0][0] !== closedRing[closedRing.length - 1][0] ||
|
|
|
+ closedRing[0][1] !== closedRing[closedRing.length - 1][1])
|
|
|
+ ) {
|
|
|
+ closedRing.push(closedRing[0]);
|
|
|
+ }
|
|
|
+ var joinedPoly = polygon([closedRing]);
|
|
|
+ that.geopolygon = joinedPoly;
|
|
|
+ var bx = bbox(joinedPoly);
|
|
|
+ that.boxAll = bx;
|
|
|
+ };
|
|
|
+
|
|
|
+ // ===== 日照分析:驱动已挂载组件(分析引擎=组件方法,流程与页面操作一致) =====
|
|
|
+ window.__sunlightRun = async function (opts) {
|
|
|
+ // var that = window.__sunlightInstance;
|
|
|
+ var that = window.__findSunlightInstance();
|
|
|
+ return new Promise(function (resolve, reject) {
|
|
|
+ if (!that) {
|
|
|
+ reject(new Error("未找到日照分析实例"));
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ (async function () {
|
|
|
+ try {
|
|
|
+ if (opts && opts.geojson) {
|
|
|
+ // __sunlightSetupRange 是 async(含 sampleTerrain 采样高程设置 boxAll),必须 await 完再分析
|
|
|
+ await window.__sunlightSetupRange(opts.geojson);
|
|
|
+ }
|
|
|
+ if (opts && opts.date) {
|
|
|
+ that.form.selDate = new Date(opts.date);
|
|
|
+ if (opts.startTime != null) that.form.startTime = opts.startTime;
|
|
|
+ if (opts.endTime != null) that.form.endTime = opts.endTime;
|
|
|
+ }
|
|
|
+ setTimeout(function () {
|
|
|
+ var sawLoading = false;
|
|
|
+ var start = Date.now();
|
|
|
+ // 跑采光率分析(imptAnalysis),生成日照时长彩色叠加
|
|
|
+ try {
|
|
|
+ that.analysis();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("analysis error", e);
|
|
|
+ }
|
|
|
+ // 渲染日照效果(阴影/采光率动画)
|
|
|
+ try {
|
|
|
+ that.statrSunlight();
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("statrSunlight error", e);
|
|
|
+ }
|
|
|
+ // 等分析结束(loading 由 true→false),再留几秒让彩色叠加渲染完,然后导出
|
|
|
+ var poll = setInterval(function () {
|
|
|
+ var elapsed = Date.now() - start;
|
|
|
+ if (that.loading) sawLoading = true;
|
|
|
+ if ((sawLoading && !that.loading) || elapsed > 180000) {
|
|
|
+ clearInterval(poll);
|
|
|
+ setTimeout(function () {
|
|
|
+ that.exportResultDataUrl().then(resolve).catch(reject);
|
|
|
+ }, 3000);
|
|
|
+ }
|
|
|
+ }, 1000);
|
|
|
+ }, 800);
|
|
|
+ } catch (e) {
|
|
|
+ reject(e);
|
|
|
+ }
|
|
|
+ })();
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // ===== 天际线分析:纯 Cesium Facade(无需组件实例) =====
|
|
|
+ window.__skylineRun = function (params) {
|
|
|
+ return (async function () {
|
|
|
+ var start = params.start; // [lon,lat]
|
|
|
+ var end = params.end; // [lon,lat]
|
|
|
+ var faceH = params.faceH || 100;
|
|
|
+ var faceW = params.faceW || 200;
|
|
|
+ var slider = params.slider || "left";
|
|
|
+
|
|
|
+ // 1) 采样两点 DSM 地形高程(facade 相机贴地才拍得到楼)
|
|
|
+ var cartos = [
|
|
|
+ Cesium.Cartographic.fromDegrees(start[0], start[1]),
|
|
|
+ Cesium.Cartographic.fromDegrees(end[0], end[1]),
|
|
|
+ ];
|
|
|
+ if (window.viewer.terrainProvider && window.viewer.terrainProvider.visible) {
|
|
|
+ try {
|
|
|
+ await Cesium.sampleTerrainMostDetailed(
|
|
|
+ window.viewer.terrainProvider,
|
|
|
+ cartos
|
|
|
+ );
|
|
|
+ } catch (e) {
|
|
|
+ console.warn("sampleTerrain error", e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ var h1 =
|
|
|
+ cartos[0] && typeof cartos[0].height === "number"
|
|
|
+ ? cartos[0].height
|
|
|
+ : start[2] || 0;
|
|
|
+ var h2 =
|
|
|
+ cartos[1] && typeof cartos[1].height === "number"
|
|
|
+ ? cartos[1].height
|
|
|
+ : end[2] || 0;
|
|
|
+
|
|
|
+ // 2) 相机先飞到线中点附近,等倾斜摄影瓦片加载稳定。
|
|
|
+ // facade 渲染不等 S3M 瓦片流,不预载会拍到空场景(全黑图)。
|
|
|
+ var midLon = (start[0] + end[0]) / 2;
|
|
|
+ var midLat = (start[1] + end[1]) / 2;
|
|
|
+ viewer.camera.setView({
|
|
|
+ destination: Cesium.Cartesian3.fromDegrees(
|
|
|
+ midLon,
|
|
|
+ midLat - 0.003,
|
|
|
+ Math.max(h1, h2) + parseFloat(faceH) + 60
|
|
|
+ ),
|
|
|
+ orientation: {
|
|
|
+ heading: 0,
|
|
|
+ pitch: Cesium.Math.toRadians(-25),
|
|
|
+ roll: 0,
|
|
|
+ },
|
|
|
+ });
|
|
|
+ await waitTilesLoaded(viewer.scene);
|
|
|
+
|
|
|
+ // 3) 建 facade 并设置分析参数(与交互 draw() 同序)
|
|
|
+ var facade = new Cesium.Facade(scene);
|
|
|
+ facade.build();
|
|
|
+ facade.setBoundByPoints(null, null);
|
|
|
+ var startPoint = Cesium.Cartesian3.fromDegrees(start[0], start[1], h1);
|
|
|
+ var endPoint = Cesium.Cartesian3.fromDegrees(end[0], end[1], h2);
|
|
|
+ if (slider == "left") facade.setBoundByPoints(startPoint, endPoint);
|
|
|
+ else facade.setBoundByPoints(endPoint, startPoint);
|
|
|
+ facade.maxHeight = parseFloat(faceH);
|
|
|
+ facade.farDistance = parseFloat(faceW);
|
|
|
+
|
|
|
+ // 4) facade 每次帧捕获会重建 readyPromise(_beginFunc 里 defer),
|
|
|
+ // 最终 resolve 的是最新 defer。必须等捕获完成(_fbo._isUpdate 变
|
|
|
+ // false 是完成信号),再取最终 readyPromise,否则永远挂起。
|
|
|
+ var started = Date.now();
|
|
|
+ return await new Promise(function (resolve2, reject2) {
|
|
|
+ var waitDone = function () {
|
|
|
+ var fbo = facade._fbo;
|
|
|
+ var done = fbo && fbo._isUpdate === false;
|
|
|
+ var timeout = Date.now() - started > 60000;
|
|
|
+ if (done || timeout) {
|
|
|
+ Promise.resolve(facade.readyPromise)
|
|
|
+ .then(function (base64data) {
|
|
|
+ resolve2(base64data); // 天际线结果图 data URL
|
|
|
+ })
|
|
|
+ .catch(function (e) {
|
|
|
+ reject2(e);
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ setTimeout(waitDone, 200);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ setTimeout(waitDone, 500);
|
|
|
+ });
|
|
|
+ })();
|
|
|
+ };
|
|
|
+
|
|
|
+ console.log("[page-hooks] 注入完成:__sunlightRun / __skylineRun");
|
|
|
+})();
|