CesiumWidget.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795
  1. import buildModuleUrl from "../../Core/buildModuleUrl.js";
  2. import Cartesian3 from "../../Core/Cartesian3.js";
  3. import Clock from "../../Core/Clock.js";
  4. import defaultValue from "../../Core/defaultValue.js";
  5. import defined from "../../Core/defined.js";
  6. import destroyObject from "../../Core/destroyObject.js";
  7. import DeveloperError from "../../Core/DeveloperError.js";
  8. import Ellipsoid from "../../Core/Ellipsoid.js";
  9. import FeatureDetection from "../../Core/FeatureDetection.js";
  10. import formatError from "../../Core/formatError.js";
  11. import requestAnimationFrame from "../../Core/requestAnimationFrame.js";
  12. import ScreenSpaceEventHandler from "../../Core/ScreenSpaceEventHandler.js";
  13. import createWorldImagery from "../../Scene/createWorldImagery.js";
  14. import Globe from "../../Scene/Globe.js";
  15. import Moon from "../../Scene/Moon.js";
  16. import Scene from "../../Scene/Scene.js";
  17. import SceneMode from "../../Scene/SceneMode.js";
  18. import ShadowMode from "../../Scene/ShadowMode.js";
  19. import SkyAtmosphere from "../../Scene/SkyAtmosphere.js";
  20. import SkyBox from "../../Scene/SkyBox.js";
  21. import Sun from "../../Scene/Sun.js";
  22. import getElement from "../getElement.js";
  23. function getDefaultSkyBoxUrl(suffix) {
  24. return buildModuleUrl(`Assets/Textures/SkyBox/tycho2t3_80_${suffix}.jpg`);
  25. }
  26. function startRenderLoop(widget) {
  27. widget._renderLoopRunning = true;
  28. let lastFrameTime = 0;
  29. function render(frameTime) {
  30. if (widget.isDestroyed()) {
  31. return;
  32. }
  33. if (widget._useDefaultRenderLoop) {
  34. try {
  35. const targetFrameRate = widget._targetFrameRate;
  36. if (!defined(targetFrameRate)) {
  37. widget.resize();
  38. widget.render();
  39. requestAnimationFrame(render);
  40. } else {
  41. const interval = 1000.0 / targetFrameRate;
  42. const delta = frameTime - lastFrameTime;
  43. if (delta > interval) {
  44. widget.resize();
  45. widget.render();
  46. lastFrameTime = frameTime - (delta % interval);
  47. }
  48. requestAnimationFrame(render);
  49. }
  50. } catch (error) {
  51. widget._useDefaultRenderLoop = false;
  52. widget._renderLoopRunning = false;
  53. if (widget._showRenderLoopErrors) {
  54. const title =
  55. "An error occurred while rendering. Rendering has stopped.";
  56. widget.showErrorPanel(title, undefined, error);
  57. }
  58. }
  59. } else {
  60. widget._renderLoopRunning = false;
  61. }
  62. }
  63. requestAnimationFrame(render);
  64. }
  65. function configurePixelRatio(widget) {
  66. let pixelRatio = widget._useBrowserRecommendedResolution
  67. ? 1.0
  68. : window.devicePixelRatio;
  69. pixelRatio *= widget._resolutionScale;
  70. if (defined(widget._scene)) {
  71. widget._scene.pixelRatio = pixelRatio;
  72. }
  73. return pixelRatio;
  74. }
  75. function configureCanvasSize(widget) {
  76. const canvas = widget._canvas;
  77. let width = canvas.clientWidth;
  78. let height = canvas.clientHeight;
  79. const pixelRatio = configurePixelRatio(widget);
  80. widget._canvasClientWidth = width;
  81. widget._canvasClientHeight = height;
  82. width *= pixelRatio;
  83. height *= pixelRatio;
  84. canvas.width = width;
  85. canvas.height = height;
  86. widget._canRender = width !== 0 && height !== 0;
  87. widget._lastDevicePixelRatio = window.devicePixelRatio;
  88. }
  89. function configureCameraFrustum(widget) {
  90. const canvas = widget._canvas;
  91. const width = canvas.width;
  92. const height = canvas.height;
  93. if (width !== 0 && height !== 0) {
  94. const frustum = widget._scene.camera.frustum;
  95. if (defined(frustum.aspectRatio)) {
  96. frustum.aspectRatio = width / height;
  97. } else {
  98. frustum.top = frustum.right * (height / width);
  99. frustum.bottom = -frustum.top;
  100. }
  101. }
  102. }
  103. /**
  104. * A widget containing a Cesium scene.
  105. *
  106. * @alias CesiumWidget
  107. * @constructor
  108. *
  109. * @param {Element|String} container The DOM element or ID that will contain the widget.
  110. * @param {Object} [options] Object with the following properties:
  111. * @param {Clock} [options.clock=new Clock()] The clock to use to control current time.
  112. * @param {ImageryProvider | false} [options.imageryProvider=createWorldImagery()] The imagery provider to serve as the base layer. If set to <code>false</code>, no imagery provider will be added.
  113. * @param {TerrainProvider} [options.terrainProvider=new EllipsoidTerrainProvider] The terrain provider.
  114. * @param {SkyBox| false} [options.skyBox] The skybox used to render the stars. When <code>undefined</code>, the default stars are used. If set to <code>false</code>, no skyBox, Sun, or Moon will be added.
  115. * @param {SkyAtmosphere | false} [options.skyAtmosphere] Blue sky, and the glow around the Earth's limb. Set to <code>false</code> to turn it off.
  116. * @param {SceneMode} [options.sceneMode=SceneMode.SCENE3D] The initial scene mode.
  117. * @param {Boolean} [options.scene3DOnly=false] When <code>true</code>, each geometry instance will only be rendered in 3D to save GPU memory.
  118. * @param {Boolean} [options.orderIndependentTranslucency=true] If true and the configuration supports it, use order independent translucency.
  119. * @param {MapProjection} [options.mapProjection=new GeographicProjection()] The map projection to use in 2D and Columbus View modes.
  120. * @param {Globe | false} [options.globe=new Globe(mapProjection.ellipsoid)] The globe to use in the scene. If set to <code>false</code>, no globe will be added.
  121. * @param {Boolean} [options.useDefaultRenderLoop=true] True if this widget should control the render loop, false otherwise.
  122. * @param {Boolean} [options.useBrowserRecommendedResolution=true] If true, render at the browser's recommended resolution and ignore <code>window.devicePixelRatio</code>.
  123. * @param {Number} [options.targetFrameRate] The target frame rate when using the default render loop.
  124. * @param {Boolean} [options.showRenderLoopErrors=true] If true, this widget will automatically display an HTML panel to the user containing the error, if a render loop error occurs.
  125. * @param {Object} [options.contextOptions] Context and WebGL creation properties corresponding to <code>options</code> passed to {@link Scene}.
  126. * @param {Element|String} [options.creditContainer] The DOM element or ID that will contain the {@link CreditDisplay}. If not specified, the credits are added
  127. * to the bottom of the widget itself.
  128. * @param {Element|String} [options.creditViewport] The DOM element or ID that will contain the credit pop up created by the {@link CreditDisplay}. If not specified, it will appear over the widget itself.
  129. * @param {Boolean} [options.shadows=false] Determines if shadows are cast by light sources.
  130. * @param {ShadowMode} [options.terrainShadows=ShadowMode.RECEIVE_ONLY] Determines if the terrain casts or receives shadows from light sources.
  131. * @param {MapMode2D} [options.mapMode2D=MapMode2D.INFINITE_SCROLL] Determines if the 2D map is rotatable or can be scrolled infinitely in the horizontal direction.
  132. * @param {Boolean} [options.requestRenderMode=false] If true, rendering a frame will only occur when needed as determined by changes within the scene. Enabling improves performance of the application, but requires using {@link Scene#requestRender} to render a new frame explicitly in this mode. This will be necessary in many cases after making changes to the scene in other parts of the API. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
  133. * @param {Number} [options.maximumRenderTimeChange=0.0] If requestRenderMode is true, this value defines the maximum change in simulation time allowed before a render is requested. See {@link https://cesium.com/blog/2018/01/24/cesium-scene-rendering-performance/|Improving Performance with Explicit Rendering}.
  134. * @param {Number} [options.msaaSamples=1] If provided, this value controls the rate of multisample antialiasing. Typical multisampling rates are 2, 4, and sometimes 8 samples per pixel. Higher sampling rates of MSAA may impact performance in exchange for improved visual quality. This value only applies to WebGL2 contexts that support multisample render targets.
  135. *
  136. * @exception {DeveloperError} Element with id "container" does not exist in the document.
  137. *
  138. * @demo {@link https://sandcastle.cesium.com/index.html?src=Cesium%20Widget.html|Cesium Sandcastle Cesium Widget Demo}
  139. *
  140. * @example
  141. * // For each example, include a link to CesiumWidget.css stylesheet in HTML head,
  142. * // and in the body, include: <div id="cesiumContainer"></div>
  143. *
  144. * //Widget with no terrain and default Bing Maps imagery provider.
  145. * const widget = new Cesium.CesiumWidget('cesiumContainer');
  146. *
  147. * //Widget with ion imagery and Cesium World Terrain.
  148. * const widget2 = new Cesium.CesiumWidget('cesiumContainer', {
  149. * imageryProvider : Cesium.createWorldImagery(),
  150. * terrainProvider : Cesium.createWorldTerrain(),
  151. * skyBox : new Cesium.SkyBox({
  152. * sources : {
  153. * positiveX : 'stars/TychoSkymapII.t3_08192x04096_80_px.jpg',
  154. * negativeX : 'stars/TychoSkymapII.t3_08192x04096_80_mx.jpg',
  155. * positiveY : 'stars/TychoSkymapII.t3_08192x04096_80_py.jpg',
  156. * negativeY : 'stars/TychoSkymapII.t3_08192x04096_80_my.jpg',
  157. * positiveZ : 'stars/TychoSkymapII.t3_08192x04096_80_pz.jpg',
  158. * negativeZ : 'stars/TychoSkymapII.t3_08192x04096_80_mz.jpg'
  159. * }
  160. * }),
  161. * // Show Columbus View map with Web Mercator projection
  162. * sceneMode : Cesium.SceneMode.COLUMBUS_VIEW,
  163. * mapProjection : new Cesium.WebMercatorProjection()
  164. * });
  165. */
  166. function CesiumWidget(container, options) {
  167. //>>includeStart('debug', pragmas.debug);
  168. if (!defined(container)) {
  169. throw new DeveloperError("container is required.");
  170. }
  171. //>>includeEnd('debug');
  172. container = getElement(container);
  173. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  174. //Configure the widget DOM elements
  175. const element = document.createElement("div");
  176. element.className = "cesium-widget";
  177. container.appendChild(element);
  178. const canvas = document.createElement("canvas");
  179. const supportsImageRenderingPixelated = FeatureDetection.supportsImageRenderingPixelated();
  180. this._supportsImageRenderingPixelated = supportsImageRenderingPixelated;
  181. if (supportsImageRenderingPixelated) {
  182. canvas.style.imageRendering = FeatureDetection.imageRenderingValue();
  183. }
  184. canvas.oncontextmenu = function () {
  185. return false;
  186. };
  187. canvas.onselectstart = function () {
  188. return false;
  189. };
  190. // Interacting with a canvas does not automatically blur the previously focused element.
  191. // This leads to unexpected interaction if the last element was an input field.
  192. // For example, clicking the mouse wheel could lead to the value in the field changing
  193. // unexpectedly. The solution is to blur whatever has focus as soon as canvas interaction begins.
  194. function blurActiveElement() {
  195. if (canvas !== canvas.ownerDocument.activeElement) {
  196. canvas.ownerDocument.activeElement.blur();
  197. }
  198. }
  199. canvas.addEventListener("mousedown", blurActiveElement);
  200. canvas.addEventListener("pointerdown", blurActiveElement);
  201. element.appendChild(canvas);
  202. const innerCreditContainer = document.createElement("div");
  203. innerCreditContainer.className = "cesium-widget-credits";
  204. const creditContainer = defined(options.creditContainer)
  205. ? getElement(options.creditContainer)
  206. : element;
  207. creditContainer.appendChild(innerCreditContainer);
  208. const creditViewport = defined(options.creditViewport)
  209. ? getElement(options.creditViewport)
  210. : element;
  211. const showRenderLoopErrors = defaultValue(options.showRenderLoopErrors, true);
  212. const useBrowserRecommendedResolution = defaultValue(
  213. options.useBrowserRecommendedResolution,
  214. true
  215. );
  216. this._element = element;
  217. this._container = container;
  218. this._canvas = canvas;
  219. this._canvasClientWidth = 0;
  220. this._canvasClientHeight = 0;
  221. this._lastDevicePixelRatio = 0;
  222. this._creditViewport = creditViewport;
  223. this._creditContainer = creditContainer;
  224. this._innerCreditContainer = innerCreditContainer;
  225. this._canRender = false;
  226. this._renderLoopRunning = false;
  227. this._showRenderLoopErrors = showRenderLoopErrors;
  228. this._resolutionScale = 1.0;
  229. this._useBrowserRecommendedResolution = useBrowserRecommendedResolution;
  230. this._forceResize = false;
  231. this._clock = defined(options.clock) ? options.clock : new Clock();
  232. configureCanvasSize(this);
  233. try {
  234. const scene = new Scene({
  235. canvas: canvas,
  236. contextOptions: options.contextOptions,
  237. creditContainer: innerCreditContainer,
  238. creditViewport: creditViewport,
  239. mapProjection: options.mapProjection,
  240. orderIndependentTranslucency: options.orderIndependentTranslucency,
  241. scene3DOnly: defaultValue(options.scene3DOnly, false),
  242. shadows: options.shadows,
  243. mapMode2D: options.mapMode2D,
  244. requestRenderMode: options.requestRenderMode,
  245. maximumRenderTimeChange: options.maximumRenderTimeChange,
  246. depthPlaneEllipsoidOffset: options.depthPlaneEllipsoidOffset,
  247. msaaSamples: options.msaaSamples,
  248. });
  249. this._scene = scene;
  250. scene.camera.constrainedAxis = Cartesian3.UNIT_Z;
  251. configurePixelRatio(this);
  252. configureCameraFrustum(this);
  253. const ellipsoid = defaultValue(
  254. scene.mapProjection.ellipsoid,
  255. Ellipsoid.WGS84
  256. );
  257. let globe = options.globe;
  258. if (!defined(globe)) {
  259. globe = new Globe(ellipsoid);
  260. }
  261. if (globe !== false) {
  262. scene.globe = globe;
  263. scene.globe.shadows = defaultValue(
  264. options.terrainShadows,
  265. ShadowMode.RECEIVE_ONLY
  266. );
  267. }
  268. let skyBox = options.skyBox;
  269. if (!defined(skyBox)) {
  270. skyBox = new SkyBox({
  271. sources: {
  272. positiveX: getDefaultSkyBoxUrl("px"),
  273. negativeX: getDefaultSkyBoxUrl("mx"),
  274. positiveY: getDefaultSkyBoxUrl("py"),
  275. negativeY: getDefaultSkyBoxUrl("my"),
  276. positiveZ: getDefaultSkyBoxUrl("pz"),
  277. negativeZ: getDefaultSkyBoxUrl("mz"),
  278. },
  279. });
  280. }
  281. if (skyBox !== false) {
  282. scene.skyBox = skyBox;
  283. scene.sun = new Sun();
  284. scene.moon = new Moon();
  285. }
  286. // Blue sky, and the glow around the Earth's limb.
  287. let skyAtmosphere = options.skyAtmosphere;
  288. if (!defined(skyAtmosphere)) {
  289. skyAtmosphere = new SkyAtmosphere(ellipsoid);
  290. }
  291. if (skyAtmosphere !== false) {
  292. scene.skyAtmosphere = skyAtmosphere;
  293. }
  294. //Set the base imagery layer
  295. let imageryProvider =
  296. options.globe === false ? false : options.imageryProvider;
  297. if (!defined(imageryProvider)) {
  298. imageryProvider = createWorldImagery();
  299. }
  300. if (imageryProvider !== false) {
  301. scene.imageryLayers.addImageryProvider(imageryProvider);
  302. }
  303. //Set the terrain provider if one is provided.
  304. if (defined(options.terrainProvider) && options.globe !== false) {
  305. scene.terrainProvider = options.terrainProvider;
  306. }
  307. this._screenSpaceEventHandler = new ScreenSpaceEventHandler(canvas);
  308. if (defined(options.sceneMode)) {
  309. if (options.sceneMode === SceneMode.SCENE2D) {
  310. this._scene.morphTo2D(0);
  311. }
  312. if (options.sceneMode === SceneMode.COLUMBUS_VIEW) {
  313. this._scene.morphToColumbusView(0);
  314. }
  315. }
  316. this._useDefaultRenderLoop = undefined;
  317. this.useDefaultRenderLoop = defaultValue(
  318. options.useDefaultRenderLoop,
  319. true
  320. );
  321. this._targetFrameRate = undefined;
  322. this.targetFrameRate = options.targetFrameRate;
  323. const that = this;
  324. this._onRenderError = function (scene, error) {
  325. that._useDefaultRenderLoop = false;
  326. that._renderLoopRunning = false;
  327. if (that._showRenderLoopErrors) {
  328. const title =
  329. "An error occurred while rendering. Rendering has stopped.";
  330. that.showErrorPanel(title, undefined, error);
  331. }
  332. };
  333. scene.renderError.addEventListener(this._onRenderError);
  334. } catch (error) {
  335. if (showRenderLoopErrors) {
  336. const title = "Error constructing CesiumWidget.";
  337. const message =
  338. 'Visit <a href="http://get.webgl.org">http://get.webgl.org</a> to verify that your web browser and hardware support WebGL. Consider trying a different web browser or updating your video drivers. Detailed error information is below:';
  339. this.showErrorPanel(title, message, error);
  340. }
  341. throw error;
  342. }
  343. }
  344. Object.defineProperties(CesiumWidget.prototype, {
  345. /**
  346. * Gets the parent container.
  347. * @memberof CesiumWidget.prototype
  348. *
  349. * @type {Element}
  350. * @readonly
  351. */
  352. container: {
  353. get: function () {
  354. return this._container;
  355. },
  356. },
  357. /**
  358. * Gets the canvas.
  359. * @memberof CesiumWidget.prototype
  360. *
  361. * @type {HTMLCanvasElement}
  362. * @readonly
  363. */
  364. canvas: {
  365. get: function () {
  366. return this._canvas;
  367. },
  368. },
  369. /**
  370. * Gets the credit container.
  371. * @memberof CesiumWidget.prototype
  372. *
  373. * @type {Element}
  374. * @readonly
  375. */
  376. creditContainer: {
  377. get: function () {
  378. return this._creditContainer;
  379. },
  380. },
  381. /**
  382. * Gets the credit viewport
  383. * @memberof CesiumWidget.prototype
  384. *
  385. * @type {Element}
  386. * @readonly
  387. */
  388. creditViewport: {
  389. get: function () {
  390. return this._creditViewport;
  391. },
  392. },
  393. /**
  394. * Gets the scene.
  395. * @memberof CesiumWidget.prototype
  396. *
  397. * @type {Scene}
  398. * @readonly
  399. */
  400. scene: {
  401. get: function () {
  402. return this._scene;
  403. },
  404. },
  405. /**
  406. * Gets the collection of image layers that will be rendered on the globe.
  407. * @memberof CesiumWidget.prototype
  408. *
  409. * @type {ImageryLayerCollection}
  410. * @readonly
  411. */
  412. imageryLayers: {
  413. get: function () {
  414. return this._scene.imageryLayers;
  415. },
  416. },
  417. /**
  418. * The terrain provider providing surface geometry for the globe.
  419. * @memberof CesiumWidget.prototype
  420. *
  421. * @type {TerrainProvider}
  422. */
  423. terrainProvider: {
  424. get: function () {
  425. return this._scene.terrainProvider;
  426. },
  427. set: function (terrainProvider) {
  428. this._scene.terrainProvider = terrainProvider;
  429. },
  430. },
  431. /**
  432. * Gets the camera.
  433. * @memberof CesiumWidget.prototype
  434. *
  435. * @type {Camera}
  436. * @readonly
  437. */
  438. camera: {
  439. get: function () {
  440. return this._scene.camera;
  441. },
  442. },
  443. /**
  444. * Gets the clock.
  445. * @memberof CesiumWidget.prototype
  446. *
  447. * @type {Clock}
  448. * @readonly
  449. */
  450. clock: {
  451. get: function () {
  452. return this._clock;
  453. },
  454. },
  455. /**
  456. * Gets the screen space event handler.
  457. * @memberof CesiumWidget.prototype
  458. *
  459. * @type {ScreenSpaceEventHandler}
  460. * @readonly
  461. */
  462. screenSpaceEventHandler: {
  463. get: function () {
  464. return this._screenSpaceEventHandler;
  465. },
  466. },
  467. /**
  468. * Gets or sets the target frame rate of the widget when <code>useDefaultRenderLoop</code>
  469. * is true. If undefined, the browser's {@link requestAnimationFrame} implementation
  470. * determines the frame rate. If defined, this value must be greater than 0. A value higher
  471. * than the underlying requestAnimationFrame implementation will have no effect.
  472. * @memberof CesiumWidget.prototype
  473. *
  474. * @type {Number}
  475. */
  476. targetFrameRate: {
  477. get: function () {
  478. return this._targetFrameRate;
  479. },
  480. set: function (value) {
  481. //>>includeStart('debug', pragmas.debug);
  482. if (value <= 0) {
  483. throw new DeveloperError(
  484. "targetFrameRate must be greater than 0, or undefined."
  485. );
  486. }
  487. //>>includeEnd('debug');
  488. this._targetFrameRate = value;
  489. },
  490. },
  491. /**
  492. * Gets or sets whether or not this widget should control the render loop.
  493. * If set to true the widget will use {@link requestAnimationFrame} to
  494. * perform rendering and resizing of the widget, as well as drive the
  495. * simulation clock. If set to false, you must manually call the
  496. * <code>resize</code>, <code>render</code> methods as part of a custom
  497. * render loop. If an error occurs during rendering, {@link Scene}'s
  498. * <code>renderError</code> event will be raised and this property
  499. * will be set to false. It must be set back to true to continue rendering
  500. * after the error.
  501. * @memberof CesiumWidget.prototype
  502. *
  503. * @type {Boolean}
  504. */
  505. useDefaultRenderLoop: {
  506. get: function () {
  507. return this._useDefaultRenderLoop;
  508. },
  509. set: function (value) {
  510. if (this._useDefaultRenderLoop !== value) {
  511. this._useDefaultRenderLoop = value;
  512. if (value && !this._renderLoopRunning) {
  513. startRenderLoop(this);
  514. }
  515. }
  516. },
  517. },
  518. /**
  519. * Gets or sets a scaling factor for rendering resolution. Values less than 1.0 can improve
  520. * performance on less powerful devices while values greater than 1.0 will render at a higher
  521. * resolution and then scale down, resulting in improved visual fidelity.
  522. * For example, if the widget is laid out at a size of 640x480, setting this value to 0.5
  523. * will cause the scene to be rendered at 320x240 and then scaled up while setting
  524. * it to 2.0 will cause the scene to be rendered at 1280x960 and then scaled down.
  525. * @memberof CesiumWidget.prototype
  526. *
  527. * @type {Number}
  528. * @default 1.0
  529. */
  530. resolutionScale: {
  531. get: function () {
  532. return this._resolutionScale;
  533. },
  534. set: function (value) {
  535. //>>includeStart('debug', pragmas.debug);
  536. if (value <= 0) {
  537. throw new DeveloperError("resolutionScale must be greater than 0.");
  538. }
  539. //>>includeEnd('debug');
  540. if (this._resolutionScale !== value) {
  541. this._resolutionScale = value;
  542. this._forceResize = true;
  543. }
  544. },
  545. },
  546. /**
  547. * Boolean flag indicating if the browser's recommended resolution is used.
  548. * If true, the browser's device pixel ratio is ignored and 1.0 is used instead,
  549. * effectively rendering based on CSS pixels instead of device pixels. This can improve
  550. * performance on less powerful devices that have high pixel density. When false, rendering
  551. * will be in device pixels. {@link CesiumWidget#resolutionScale} will still take effect whether
  552. * this flag is true or false.
  553. * @memberof CesiumWidget.prototype
  554. *
  555. * @type {Boolean}
  556. * @default true
  557. */
  558. useBrowserRecommendedResolution: {
  559. get: function () {
  560. return this._useBrowserRecommendedResolution;
  561. },
  562. set: function (value) {
  563. if (this._useBrowserRecommendedResolution !== value) {
  564. this._useBrowserRecommendedResolution = value;
  565. this._forceResize = true;
  566. }
  567. },
  568. },
  569. });
  570. /**
  571. * Show an error panel to the user containing a title and a longer error message,
  572. * which can be dismissed using an OK button. This panel is displayed automatically
  573. * when a render loop error occurs, if showRenderLoopErrors was not false when the
  574. * widget was constructed.
  575. *
  576. * @param {String} title The title to be displayed on the error panel. This string is interpreted as text.
  577. * @param {String} [message] A helpful, user-facing message to display prior to the detailed error information. This string is interpreted as HTML.
  578. * @param {String} [error] The error to be displayed on the error panel. This string is formatted using {@link formatError} and then displayed as text.
  579. */
  580. CesiumWidget.prototype.showErrorPanel = function (title, message, error) {
  581. const element = this._element;
  582. const overlay = document.createElement("div");
  583. overlay.className = "cesium-widget-errorPanel";
  584. const content = document.createElement("div");
  585. content.className = "cesium-widget-errorPanel-content";
  586. overlay.appendChild(content);
  587. const errorHeader = document.createElement("div");
  588. errorHeader.className = "cesium-widget-errorPanel-header";
  589. errorHeader.appendChild(document.createTextNode(title));
  590. content.appendChild(errorHeader);
  591. const errorPanelScroller = document.createElement("div");
  592. errorPanelScroller.className = "cesium-widget-errorPanel-scroll";
  593. content.appendChild(errorPanelScroller);
  594. function resizeCallback() {
  595. errorPanelScroller.style.maxHeight = `${Math.max(
  596. Math.round(element.clientHeight * 0.9 - 100),
  597. 30
  598. )}px`;
  599. }
  600. resizeCallback();
  601. if (defined(window.addEventListener)) {
  602. window.addEventListener("resize", resizeCallback, false);
  603. }
  604. const hasMessage = defined(message);
  605. const hasError = defined(error);
  606. if (hasMessage || hasError) {
  607. const errorMessage = document.createElement("div");
  608. errorMessage.className = "cesium-widget-errorPanel-message";
  609. errorPanelScroller.appendChild(errorMessage);
  610. if (hasError) {
  611. let errorDetails = formatError(error);
  612. if (!hasMessage) {
  613. if (typeof error === "string") {
  614. error = new Error(error);
  615. }
  616. message = formatError({
  617. name: error.name,
  618. message: error.message,
  619. });
  620. errorDetails = error.stack;
  621. }
  622. //IE8 does not have a console object unless the dev tools are open.
  623. if (typeof console !== "undefined") {
  624. console.error(`${title}\n${message}\n${errorDetails}`);
  625. }
  626. const errorMessageDetails = document.createElement("div");
  627. errorMessageDetails.className =
  628. "cesium-widget-errorPanel-message-details collapsed";
  629. const moreDetails = document.createElement("span");
  630. moreDetails.className = "cesium-widget-errorPanel-more-details";
  631. moreDetails.appendChild(document.createTextNode("See more..."));
  632. errorMessageDetails.appendChild(moreDetails);
  633. errorMessageDetails.onclick = function (e) {
  634. errorMessageDetails.removeChild(moreDetails);
  635. errorMessageDetails.appendChild(document.createTextNode(errorDetails));
  636. errorMessageDetails.className =
  637. "cesium-widget-errorPanel-message-details";
  638. content.className = "cesium-widget-errorPanel-content expanded";
  639. errorMessageDetails.onclick = undefined;
  640. };
  641. errorPanelScroller.appendChild(errorMessageDetails);
  642. }
  643. errorMessage.innerHTML = `<p>${message}</p>`;
  644. }
  645. const buttonPanel = document.createElement("div");
  646. buttonPanel.className = "cesium-widget-errorPanel-buttonPanel";
  647. content.appendChild(buttonPanel);
  648. const okButton = document.createElement("button");
  649. okButton.setAttribute("type", "button");
  650. okButton.className = "cesium-button";
  651. okButton.appendChild(document.createTextNode("OK"));
  652. okButton.onclick = function () {
  653. if (defined(resizeCallback) && defined(window.removeEventListener)) {
  654. window.removeEventListener("resize", resizeCallback, false);
  655. }
  656. element.removeChild(overlay);
  657. };
  658. buttonPanel.appendChild(okButton);
  659. element.appendChild(overlay);
  660. };
  661. /**
  662. * @returns {Boolean} true if the object has been destroyed, false otherwise.
  663. */
  664. CesiumWidget.prototype.isDestroyed = function () {
  665. return false;
  666. };
  667. /**
  668. * Destroys the widget. Should be called if permanently
  669. * removing the widget from layout.
  670. */
  671. CesiumWidget.prototype.destroy = function () {
  672. if (defined(this._scene)) {
  673. this._scene.renderError.removeEventListener(this._onRenderError);
  674. this._scene = this._scene.destroy();
  675. }
  676. this._container.removeChild(this._element);
  677. this._creditContainer.removeChild(this._innerCreditContainer);
  678. destroyObject(this);
  679. };
  680. /**
  681. * Updates the canvas size, camera aspect ratio, and viewport size.
  682. * This function is called automatically as needed unless
  683. * <code>useDefaultRenderLoop</code> is set to false.
  684. */
  685. CesiumWidget.prototype.resize = function () {
  686. const canvas = this._canvas;
  687. if (
  688. !this._forceResize &&
  689. this._canvasClientWidth === canvas.clientWidth &&
  690. this._canvasClientHeight === canvas.clientHeight &&
  691. this._lastDevicePixelRatio === window.devicePixelRatio
  692. ) {
  693. return;
  694. }
  695. this._forceResize = false;
  696. configureCanvasSize(this);
  697. configureCameraFrustum(this);
  698. this._scene.requestRender();
  699. };
  700. /**
  701. * Renders the scene. This function is called automatically
  702. * unless <code>useDefaultRenderLoop</code> is set to false;
  703. */
  704. CesiumWidget.prototype.render = function () {
  705. if (this._canRender) {
  706. this._scene.initializeFrame();
  707. const currentTime = this._clock.tick();
  708. this._scene.render(currentTime);
  709. } else {
  710. this._clock.tick();
  711. }
  712. };
  713. export default CesiumWidget;