ViewShed.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582
  1. /* 引入Cesium */
  2. // import * as Cesium from 'Cesium';
  3. /* 引入克里金插值 */
  4. import kriging from '@sakitam-gis/kriging';
  5. /* 引入算法 */
  6. import * as turf from "@turf/turf";
  7. import CreateRemindertip from "../common/ReminderTip.js";
  8. /**
  9. * 视域分析类
  10. */
  11. class ViewShed {
  12. /**
  13. * 默认初始化
  14. * @param {Object} viewer 三维场景
  15. */
  16. constructor(viewer) {
  17. if (!viewer) throw new Cesium.DeveloperError('no viewer object!');
  18. // let canvas = document.getElementById("canvasMap");
  19. // if (canvas == null) {
  20. // //创建画布
  21. // canvas = document.createElement('canvas');
  22. // canvas.id = "canvasMap";
  23. // canvas.style.display = "none";
  24. // canvas.style.top = "0px";
  25. // canvas.style.position = "absolute";
  26. // /* 加入到页面 */
  27. // document.body.append(canvas);
  28. // }
  29. // this.canvasEle = canvas;
  30. this.viewer = viewer;
  31. this.handler = undefined;
  32. this.lightCamera;
  33. this.pyramid;
  34. this.frustumPrimitive;
  35. this.viewershedPolygon;
  36. }
  37. /**
  38. * 初始化handler
  39. * @ignore 忽略注释,注释不生成Doc
  40. */
  41. initHandler() {
  42. if (this.handler) {
  43. this.handler.destroy();
  44. this.handler = undefined;
  45. }
  46. let canvas = document.getElementById("canvasMap");
  47. if (canvas == null) {
  48. //创建画布
  49. canvas = document.createElement('canvas');
  50. canvas.id = "canvasMap";
  51. canvas.style.display = "none";
  52. canvas.style.top = "0px";
  53. canvas.style.position = "absolute";
  54. /* 加入到页面 */
  55. document.body.append(canvas);
  56. }
  57. this.canvasEle = canvas;
  58. }
  59. /**
  60. * @ignore 忽略注释,注释不生成Doc
  61. * @param {Object} pos0
  62. * @param {Object} pos1
  63. */
  64. ReturnDistance(pos0, pos1) {
  65. let distance = 0;
  66. let point1cartographic = Cesium.Cartographic.fromCartesian(pos0);
  67. let point2cartographic = Cesium.Cartographic.fromCartesian(pos1);
  68. let geodesic = new Cesium.EllipsoidGeodesic();
  69. geodesic.setEndPoints(point1cartographic, point2cartographic);
  70. let s = geodesic.surfaceDistance;
  71. return s;
  72. }
  73. /**
  74. * @ignore 忽略注释,注释不生成Doc
  75. * @param {Object} x
  76. * @param {Object} y
  77. * @param {Object} objectsToExclude
  78. */
  79. getHeight(x, y, objectsToExclude) {
  80. let endCartographic = Cesium.Cartographic.fromDegrees(x, y);
  81. let endHeight = this.viewer.scene.sampleHeight(
  82. endCartographic,
  83. objectsToExclude
  84. );
  85. return endHeight;
  86. }
  87. /**
  88. * @ignore 忽略注释,注释不生成Doc
  89. * @param {Object} Cartesian3
  90. */
  91. cartesian3ToDegree(Cartesian3) {
  92. let _ellipsoid = this.viewer.scene.globe.ellipsoid;
  93. let _cartographic = _ellipsoid.cartesianToCartographic(Cartesian3);
  94. let _lat = Cesium.Math.toDegrees(_cartographic.latitude);
  95. let _lng = Cesium.Math.toDegrees(_cartographic.longitude);
  96. let _alt = _cartographic.height;
  97. return [_lng, _lat, _alt];
  98. }
  99. /**
  100. * @ignore 忽略注释,注释不生成Doc
  101. * @param {Object} lng1
  102. * @param {Object} lat1
  103. * @param {Object} lng2
  104. * @param {Object} lat2
  105. */
  106. getAngle(lng1, lat1, lng2, lat2) {
  107. let dRotateAngle = Math.atan2(Math.abs(lng1 - lng2), Math.abs(lat1 - lat2));
  108. if (lng2 >= lng1) {
  109. dRotateAngle = lat2 < lat1 ? Math.PI - dRotateAngle : dRotateAngle;
  110. } else {
  111. dRotateAngle =
  112. lat2 >= lat1 ? 2 * Math.PI - dRotateAngle : Math.PI + dRotateAngle;
  113. }
  114. dRotateAngle = (dRotateAngle * 180) / Math.PI;
  115. return dRotateAngle;
  116. }
  117. /**
  118. * @ignore 忽略注释,注释不生成Doc
  119. * @param {Object} pointA
  120. * @param {Object} pointB
  121. */
  122. getPitch(pointA, pointB) {
  123. let transfrom = Cesium.Transforms.eastNorthUpToFixedFrame(pointA);
  124. const vector = Cesium.Cartesian3.subtract(
  125. pointB,
  126. pointA,
  127. new Cesium.Cartesian3()
  128. );
  129. let direction = Cesium.Matrix4.multiplyByPointAsVector(
  130. Cesium.Matrix4.inverse(transfrom, transfrom),
  131. vector,
  132. vector
  133. );
  134. Cesium.Cartesian3.normalize(direction, direction);
  135. return Cesium.Math.PI_OVER_TWO - Cesium.Math.acosClamped(direction.z);
  136. }
  137. /**
  138. * @ignore 忽略注释,注释不生成Doc
  139. */
  140. updateViewShed() {
  141. this.clear();
  142. this.setLightCamera();
  143. this.addVisualPyramid();
  144. this.createFrustum();
  145. }
  146. /**
  147. * @ignore 忽略注释,注释不生成Doc
  148. */
  149. clear() {
  150. if (this.pyramid) {
  151. this.viewer.entities.removeById(this.pyramid.id);
  152. this.pyramid = undefined;
  153. }
  154. if (this.frustumPrimitive) {
  155. this.viewer.scene.primitives.remove(this.frustumPrimitive);
  156. this.frustumPrimitive = undefined;
  157. }
  158. if (this.debugModelMatrixPrimitive) {
  159. this.viewer.scene.primitives.remove(this.debugModelMatrixPrimitive);
  160. this.debugModelMatrixPrimitive = undefined;
  161. }
  162. }
  163. /**
  164. * @ignore 忽略注释,注释不生成Doc
  165. */
  166. addVisualPyramid() {
  167. let options = this.ViewShedOptions;
  168. let position = options.viewPosition;
  169. let visualRange = Number(options.visualRange);
  170. let transform = Cesium.Transforms.eastNorthUpToFixedFrame(position);
  171. this.debugModelMatrixPrimitive = this.viewer.scene.primitives.add(
  172. new Cesium.DebugModelMatrixPrimitive({
  173. modelMatrix: transform,
  174. length: 5.0,
  175. })
  176. );
  177. const halfClock = options.horizontalViewAngle / 2;
  178. const halfCone = options.verticalViewAngle / 2;
  179. const pitch = Cesium.Math.toDegrees(options.pitch);
  180. const ellipsoid = new Cesium.EllipsoidGraphics({
  181. radii: new Cesium.Cartesian3(visualRange, visualRange, visualRange),
  182. minimumClock: Cesium.Math.toRadians(90 - options.direction - halfClock),
  183. maximumClock: Cesium.Math.toRadians(90 - options.direction + halfClock),
  184. minimumCone: Cesium.Math.toRadians(90 - pitch - halfCone),
  185. maximumCone: Cesium.Math.toRadians(90 - pitch + halfCone),
  186. fill: false,
  187. outline: true,
  188. subdivisions: 256,
  189. stackPartitions: 64,
  190. slicePartitions: 64,
  191. outlineColor: Cesium.Color.YELLOWGREEN.withAlpha(0.5),
  192. });
  193. const pyramidEntity = new Cesium.Entity({
  194. position: position,
  195. ellipsoid,
  196. });
  197. this.pyramid = this.viewer.entities.add(pyramidEntity);
  198. }
  199. /**
  200. * @ignore 忽略注释,注释不生成Doc
  201. */
  202. setLightCamera() {
  203. if (!this.lightCamera) {
  204. this.lightCamera = new Cesium.Camera(this.viewer.scene);
  205. }
  206. let options = this.ViewShedOptions;
  207. let visualRange = Number(options.visualRange);
  208. this.lightCamera.position = options.viewPosition;
  209. this.lightCamera.frustum.near = 0.1;
  210. this.lightCamera.frustum.far = visualRange;
  211. const hr = Cesium.Math.toRadians(options.horizontalViewAngle);
  212. const vr = Cesium.Math.toRadians(options.verticalViewAngle);
  213. this.lightCamera.frustum.aspectRatio =
  214. (visualRange * Math.tan(hr / 2) * 2) /
  215. (visualRange * Math.tan(vr / 2) * 2);
  216. this.lightCamera.frustum.fov = hr > vr ? hr : vr;
  217. this.lightCamera.setView({
  218. destination: options.viewPosition,
  219. orientation: {
  220. heading: Cesium.Math.toRadians(options.direction || 0),
  221. pitch: options.pitch || 0,
  222. roll: 0,
  223. },
  224. });
  225. }
  226. /**
  227. * @ignore 忽略注释,注释不生成Doc
  228. */
  229. createFrustum() {
  230. const scratchRight = new Cesium.Cartesian3();
  231. const scratchRotation = new Cesium.Matrix3();
  232. const scratchOrientation = new Cesium.Quaternion();
  233. const direction = this.lightCamera.directionWC;
  234. const up = this.lightCamera.upWC;
  235. let right = this.lightCamera.rightWC;
  236. right = Cesium.Cartesian3.negate(right, scratchRight);
  237. let rotation = scratchRotation;
  238. Cesium.Matrix3.setColumn(rotation, 0, right, rotation);
  239. Cesium.Matrix3.setColumn(rotation, 1, up, rotation);
  240. Cesium.Matrix3.setColumn(rotation, 2, direction, rotation);
  241. let orientation = Cesium.Quaternion.fromRotationMatrix(
  242. rotation,
  243. scratchOrientation
  244. );
  245. let instanceOutline = new Cesium.GeometryInstance({
  246. geometry: new Cesium.FrustumOutlineGeometry({
  247. frustum: this.lightCamera.frustum,
  248. origin: this.ViewShedOptions.viewPosition,
  249. orientation: orientation,
  250. }),
  251. id: "视椎体轮廓线" + Math.random().toString(36).substr(2),
  252. attributes: {
  253. color: Cesium.ColorGeometryInstanceAttribute.fromColor(
  254. new Cesium.Color(0.0, 1.0, 0.0, 1.0)
  255. ),
  256. show: new Cesium.ShowGeometryInstanceAttribute(true),
  257. },
  258. });
  259. this.frustumPrimitive = this.viewer.scene.primitives.add(
  260. new Cesium.Primitive({
  261. geometryInstances: instanceOutline,
  262. appearance: new Cesium.PerInstanceColorAppearance({
  263. flat: true,
  264. translucent: false,
  265. closed: true,
  266. }),
  267. })
  268. );
  269. }
  270. /**
  271. * @ignore 忽略注释,注释不生成Doc
  272. */
  273. addViewershedPolygon(positionArr) {
  274. let polygon = new Cesium.PolygonGeometry({
  275. polygonHierarchy: new Cesium.PolygonHierarchy(
  276. Cesium.Cartesian3.fromDegreesArray(positionArr)
  277. ),
  278. height: 0.0,
  279. extrudedHeight: 0.0,
  280. vertexFormat: Cesium.PerInstanceColorAppearance.VERTEX_FORMAT,
  281. stRotation: 0.0,
  282. ellipsoid: Cesium.Ellipsoid.WGS84,
  283. granularity: Cesium.Math.RADIANS_PER_DEGREE,
  284. perPositionHeight: false,
  285. closeTop: true,
  286. closeBottom: true,
  287. arcType: Cesium.ArcType.GEODESIC,
  288. });
  289. let polygonInstance = new Cesium.GeometryInstance({
  290. geometry: polygon,
  291. name: "ViewershedPolygon",
  292. attributes: {
  293. color: Cesium.ColorGeometryInstanceAttribute.fromColor(
  294. Cesium.Color.BLUE.withAlpha(0.6)
  295. ),
  296. show: new Cesium.ShowGeometryInstanceAttribute(true),
  297. },
  298. });
  299. this.viewershedPolygon = this.viewer.scene.primitives.add(
  300. new Cesium.GroundPrimitive({
  301. geometryInstances: polygonInstance,
  302. appearance: new Cesium.EllipsoidSurfaceAppearance({
  303. aboveGround: true,
  304. material: new Cesium.Material({
  305. fabric: {
  306. type: "Image",
  307. uniforms: {
  308. image: this.returnImgae(),
  309. },
  310. },
  311. }),
  312. }),
  313. })
  314. );
  315. }
  316. /**
  317. * @ignore 忽略注释,注释不生成Doc
  318. */
  319. drawViewershed(precision) {
  320. const pos = this.cartesian3ToDegree(this.ViewShedOptions.viewPosition);
  321. const radius = this.ViewShedOptions.visualRange;
  322. const direction = this.ViewShedOptions.direction;
  323. let boundary = this.computeBoundaryOptions(pos, radius, direction);
  324. const bbox = boundary.bbox;
  325. let mask = turf.polygon([boundary.boundaryPoints]);
  326. const dis = this.ViewShedOptions.visualRange / (precision * 1000);
  327. let gridPoints = turf.pointGrid(bbox, dis, {
  328. mask: mask
  329. });
  330. console.log(this.ViewShedOptions.visualRange, precision, dis);
  331. let pointsResult = this.createTargetPoints(gridPoints, dis, pos);
  332. let variogram = kriging.train(
  333. pointsResult.values,
  334. pointsResult.lngs,
  335. pointsResult.lats,
  336. "exponential",
  337. 0,
  338. 100
  339. );
  340. let grid = kriging.grid([boundary.boundaryPoints], variogram, dis / 1000);
  341. const colors = [
  342. "#ff000080",
  343. "#ff000080",
  344. "#ff000080",
  345. "#ff000080",
  346. "#ff000080",
  347. "#ff000080",
  348. "#00ff0080",
  349. "#00ff0080",
  350. "#00ff0080",
  351. "#00ff0080",
  352. "#00ff0080",
  353. "#00ff0080",
  354. ];
  355. this.canvasEle.width = 3840;
  356. this.canvasEle.height = 2160;
  357. kriging.plot(
  358. this.canvasEle,
  359. grid,
  360. [bbox[0], bbox[2]],
  361. [bbox[1], bbox[3]],
  362. colors
  363. );
  364. this.addViewershedPolygon(boundary.positionArr);
  365. }
  366. /**
  367. * @ignore 忽略注释,注释不生成Doc
  368. */
  369. computeBoundaryOptions(pos, radius, angle) {
  370. let Ea = 6378137; // 赤道半径
  371. let Eb = 6356725; // 极半径
  372. const lng = pos[0],
  373. lat = pos[1];
  374. const bbox = [lng, lat, lng, lat];
  375. let positionArr = [];
  376. let boundaryPoints = [];
  377. positionArr.push(lng, lat);
  378. boundaryPoints.push([lng, lat]);
  379. //正北是0°
  380. let start = angle + 45 > 360 ? angle - 45 - 360 : angle - 45;
  381. let end = start + 90;
  382. for (let i = start; i <= end; i++) {
  383. let dx = radius * Math.sin((i * Math.PI) / 180.0);
  384. let dy = radius * Math.cos((i * Math.PI) / 180.0);
  385. let ec = Eb + ((Ea - Eb) * (90.0 - lat)) / 90.0;
  386. let ed = ec * Math.cos((lat * Math.PI) / 180);
  387. let BJD = lng + ((dx / ed) * 180.0) / Math.PI;
  388. let BWD = lat + ((dy / ec) * 180.0) / Math.PI;
  389. positionArr.push(BJD, BWD);
  390. boundaryPoints.push([BJD, BWD]);
  391. this.refreshBBox(bbox, BJD, BWD);
  392. }
  393. boundaryPoints.push([lng, lat]);
  394. return {
  395. positionArr,
  396. boundaryPoints,
  397. bbox,
  398. };
  399. }
  400. /**
  401. * 更新外围矩形 Bbox
  402. * @ignore 忽略注释,注释不生成Doc
  403. * @param {Array} result 外围矩形Bbox-[minX, minY, maxX, maxY]
  404. * @param {Number} x 经度
  405. * @param {Number} y 纬度
  406. */
  407. refreshBBox(result, x, y) {
  408. result[0] = x < result[0] ? x : result[0];
  409. result[1] = y < result[1] ? y : result[1];
  410. result[2] = x > result[2] ? x : result[2];
  411. result[3] = y > result[3] ? y : result[3];
  412. }
  413. /**
  414. * 插值点用射线判断通视性
  415. * @ignore 忽略注释,注释不生成Doc
  416. * @param {*} gridPoints 网格点
  417. * @param {*} step 步长,可以理解成是精度
  418. * @param {*} sourcePos 视域分析起点
  419. * @returns kriging插值所需的参数对象{ values:[], lngs:[], lats:[]}
  420. */
  421. createTargetPoints(gridPoints, step, sourcePos) {
  422. let positionArr = [];
  423. let objectsToExclude = [
  424. this.frustumPrimitive,
  425. this.pyramid,
  426. this.debugModelMatrixPrimitive,
  427. ];
  428. let values = [],
  429. lngs = [],
  430. lats = [];
  431. let height = this.getHeight(sourcePos[0], sourcePos[1], objectsToExclude);
  432. positionArr.push({
  433. x: sourcePos[0],
  434. y: sourcePos[1],
  435. z: height,
  436. });
  437. let viewPoint = this.ViewShedOptions.viewPosition;
  438. for (let index = 0; index < gridPoints.features.length; index++) {
  439. const feature = gridPoints.features[index];
  440. const coords = feature.geometry.coordinates;
  441. const x = coords[0],
  442. y = coords[1];
  443. let h = this.getHeight(x, y, objectsToExclude);
  444. let endPoint = Cesium.Cartesian3.fromDegrees(x, y, h);
  445. let direction = Cesium.Cartesian3.normalize(
  446. Cesium.Cartesian3.subtract(
  447. endPoint,
  448. viewPoint,
  449. new Cesium.Cartesian3()
  450. ),
  451. new Cesium.Cartesian3()
  452. );
  453. // 建立射线
  454. let ray = new Cesium.Ray(viewPoint, direction);
  455. let result = this.viewer.scene.pickFromRay(ray, objectsToExclude); // 计算交互点,返回第一个
  456. if (result) {
  457. let buffer = this.ReturnDistance(endPoint, result.position);
  458. // let M_color = Cesium.Color.GREEN;
  459. if (buffer > step) {
  460. // M_color = Cesium.Color.RED;
  461. values.push(0);
  462. } else {
  463. values.push(1);
  464. }
  465. lngs.push(x);
  466. lats.push(y);
  467. }
  468. }
  469. return {
  470. values,
  471. lngs,
  472. lats,
  473. };
  474. }
  475. /**
  476. * @ignore 忽略注释,注释不生成Doc
  477. * @returns base64图片
  478. */
  479. returnImgae() {
  480. return this.canvasEle.toDataURL("image/png");
  481. }
  482. }
  483. /**
  484. * 通用对外公开函数
  485. */
  486. Object.assign(ViewShed.prototype, /** @lends ViewShed.prototype */ {
  487. /**
  488. * 开始执行视域分析
  489. * @param {number} precision 精度,值越大创建耗时越长,建议在10~20之间
  490. */
  491. createViewshed: function(precision) {
  492. let _self = this;
  493. let scene = _self.viewer.scene;
  494. _self.initHandler();
  495. _self.clearAll();
  496. let count = 0;
  497. let toolTip = "左键点击创建起点";
  498. _self.handler = new Cesium.ScreenSpaceEventHandler(_self.viewer.canvas);
  499. _self.handler.setInputAction((event) => {
  500. count++;
  501. if (count === 1) {
  502. toolTip = "左键点击创建终点";
  503. let earthPosition = scene.pickPosition(event.position);
  504. let pos = _self.cartesian3ToDegree(earthPosition);
  505. _self.handler.setInputAction(function(move) {
  506. CreateRemindertip(toolTip, move.endPosition, true);
  507. let newPosition = scene.pickPosition(move.endPosition);
  508. if (Cesium.defined(newPosition)) {
  509. let pos1 = _self.cartesian3ToDegree(newPosition);
  510. let distance = Cesium.Cartesian3.distance(newPosition, earthPosition);
  511. let angle = _self.getAngle(pos[0], pos[1], pos1[0], pos1[1]);
  512. let pitch = _self.getPitch(earthPosition, newPosition);
  513. _self.ViewShedOptions = {
  514. viewPosition: earthPosition,
  515. endPosition: newPosition,
  516. direction: angle,
  517. pitch: pitch,
  518. horizontalViewAngle: 90,
  519. verticalViewAngle: 60,
  520. visibleAreaColor: Cesium.Color.GREEN,
  521. invisibleAreaColor: Cesium.Color.RED,
  522. visualRange: distance,
  523. };
  524. _self.updateViewShed();
  525. }
  526. }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  527. }
  528. if (count === 2) {
  529. _self.initHandler();
  530. _self.drawViewershed(precision);
  531. CreateRemindertip(toolTip, event.endPosition, false);
  532. }
  533. }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
  534. _self.handler.setInputAction(function(move) {
  535. CreateRemindertip(toolTip, move.endPosition, true);
  536. }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
  537. },
  538. /**
  539. * 清除视域分析
  540. */
  541. clearAll: function() {
  542. this.clear();
  543. if (this.viewershedPolygon) {
  544. this.viewer.scene.primitives.remove(this.viewershedPolygon);
  545. this.viewershedPolygon = undefined;
  546. }
  547. },
  548. });
  549. export default ViewShed;