GoogleEarthEnterpriseTerrainProvider.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. import when from "../ThirdParty/when.js";
  2. import Credit from "./Credit.js";
  3. import defaultValue from "./defaultValue.js";
  4. import defined from "./defined.js";
  5. import DeveloperError from "./DeveloperError.js";
  6. import Event from "./Event.js";
  7. import GeographicTilingScheme from "./GeographicTilingScheme.js";
  8. import GoogleEarthEnterpriseMetadata from "./GoogleEarthEnterpriseMetadata.js";
  9. import GoogleEarthEnterpriseTerrainData from "./GoogleEarthEnterpriseTerrainData.js";
  10. import HeightmapTerrainData from "./HeightmapTerrainData.js";
  11. import JulianDate from "./JulianDate.js";
  12. import CesiumMath from "./Math.js";
  13. import Rectangle from "./Rectangle.js";
  14. import Request from "./Request.js";
  15. import RequestState from "./RequestState.js";
  16. import RequestType from "./RequestType.js";
  17. import Resource from "./Resource.js";
  18. import RuntimeError from "./RuntimeError.js";
  19. import TaskProcessor from "./TaskProcessor.js";
  20. import TileProviderError from "./TileProviderError.js";
  21. var TerrainState = {
  22. UNKNOWN: 0,
  23. NONE: 1,
  24. SELF: 2,
  25. PARENT: 3,
  26. };
  27. var julianDateScratch = new JulianDate();
  28. function TerrainCache() {
  29. this._terrainCache = {};
  30. this._lastTidy = JulianDate.now();
  31. }
  32. TerrainCache.prototype.add = function (quadKey, buffer) {
  33. this._terrainCache[quadKey] = {
  34. buffer: buffer,
  35. timestamp: JulianDate.now(),
  36. };
  37. };
  38. TerrainCache.prototype.get = function (quadKey) {
  39. var terrainCache = this._terrainCache;
  40. var result = terrainCache[quadKey];
  41. if (defined(result)) {
  42. delete this._terrainCache[quadKey];
  43. return result.buffer;
  44. }
  45. };
  46. TerrainCache.prototype.tidy = function () {
  47. JulianDate.now(julianDateScratch);
  48. if (JulianDate.secondsDifference(julianDateScratch, this._lastTidy) > 10) {
  49. var terrainCache = this._terrainCache;
  50. var keys = Object.keys(terrainCache);
  51. var count = keys.length;
  52. for (var i = 0; i < count; ++i) {
  53. var k = keys[i];
  54. var e = terrainCache[k];
  55. if (JulianDate.secondsDifference(julianDateScratch, e.timestamp) > 10) {
  56. delete terrainCache[k];
  57. }
  58. }
  59. JulianDate.clone(julianDateScratch, this._lastTidy);
  60. }
  61. };
  62. /**
  63. * Provides tiled terrain using the Google Earth Enterprise REST API.
  64. *
  65. * @alias GoogleEarthEnterpriseTerrainProvider
  66. * @constructor
  67. *
  68. * @param {Object} options Object with the following properties:
  69. * @param {Resource|String} options.url The url of the Google Earth Enterprise server hosting the imagery.
  70. * @param {GoogleEarthEnterpriseMetadata} options.metadata A metadata object that can be used to share metadata requests with a GoogleEarthEnterpriseImageryProvider.
  71. * @param {Ellipsoid} [options.ellipsoid] The ellipsoid. If not specified, the WGS84 ellipsoid is used.
  72. * @param {Credit|String} [options.credit] A credit for the data source, which is displayed on the canvas.
  73. *
  74. * @see GoogleEarthEnterpriseImageryProvider
  75. * @see CesiumTerrainProvider
  76. *
  77. * @example
  78. * var geeMetadata = new GoogleEarthEnterpriseMetadata('http://www.earthenterprise.org/3d');
  79. * var gee = new Cesium.GoogleEarthEnterpriseTerrainProvider({
  80. * metadata : geeMetadata
  81. * });
  82. *
  83. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  84. */
  85. function GoogleEarthEnterpriseTerrainProvider(options) {
  86. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  87. //>>includeStart('debug', pragmas.debug);
  88. if (!(defined(options.url) || defined(options.metadata))) {
  89. throw new DeveloperError("options.url or options.metadata is required.");
  90. }
  91. //>>includeEnd('debug');
  92. var metadata;
  93. if (defined(options.metadata)) {
  94. metadata = options.metadata;
  95. } else {
  96. var resource = Resource.createIfNeeded(options.url);
  97. metadata = new GoogleEarthEnterpriseMetadata(resource);
  98. }
  99. this._metadata = metadata;
  100. this._tilingScheme = new GeographicTilingScheme({
  101. numberOfLevelZeroTilesX: 2,
  102. numberOfLevelZeroTilesY: 2,
  103. rectangle: new Rectangle(
  104. -CesiumMath.PI,
  105. -CesiumMath.PI,
  106. CesiumMath.PI,
  107. CesiumMath.PI
  108. ),
  109. ellipsoid: options.ellipsoid,
  110. });
  111. var credit = options.credit;
  112. if (typeof credit === "string") {
  113. credit = new Credit(credit);
  114. }
  115. this._credit = credit;
  116. // Pulled from Google's documentation
  117. this._levelZeroMaximumGeometricError = 40075.16;
  118. this._terrainCache = new TerrainCache();
  119. this._terrainPromises = {};
  120. this._terrainRequests = {};
  121. this._errorEvent = new Event();
  122. this._ready = false;
  123. var that = this;
  124. var metadataError;
  125. this._readyPromise = metadata.readyPromise
  126. .then(function (result) {
  127. if (!metadata.terrainPresent) {
  128. var e = new RuntimeError(
  129. "The server " + metadata.url + " doesn't have terrain"
  130. );
  131. metadataError = TileProviderError.handleError(
  132. metadataError,
  133. that,
  134. that._errorEvent,
  135. e.message,
  136. undefined,
  137. undefined,
  138. undefined,
  139. e
  140. );
  141. return when.reject(e);
  142. }
  143. TileProviderError.handleSuccess(metadataError);
  144. that._ready = result;
  145. return result;
  146. })
  147. .otherwise(function (e) {
  148. metadataError = TileProviderError.handleError(
  149. metadataError,
  150. that,
  151. that._errorEvent,
  152. e.message,
  153. undefined,
  154. undefined,
  155. undefined,
  156. e
  157. );
  158. return when.reject(e);
  159. });
  160. }
  161. Object.defineProperties(GoogleEarthEnterpriseTerrainProvider.prototype, {
  162. /**
  163. * Gets the name of the Google Earth Enterprise server url hosting the imagery.
  164. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  165. * @type {String}
  166. * @readonly
  167. */
  168. url: {
  169. get: function () {
  170. return this._metadata.url;
  171. },
  172. },
  173. /**
  174. * Gets the proxy used by this provider.
  175. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  176. * @type {Proxy}
  177. * @readonly
  178. */
  179. proxy: {
  180. get: function () {
  181. return this._metadata.proxy;
  182. },
  183. },
  184. /**
  185. * Gets the tiling scheme used by this provider. This function should
  186. * not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true.
  187. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  188. * @type {TilingScheme}
  189. * @readonly
  190. */
  191. tilingScheme: {
  192. get: function () {
  193. //>>includeStart('debug', pragmas.debug);
  194. if (!this._ready) {
  195. throw new DeveloperError(
  196. "tilingScheme must not be called before the imagery provider is ready."
  197. );
  198. }
  199. //>>includeEnd('debug');
  200. return this._tilingScheme;
  201. },
  202. },
  203. /**
  204. * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
  205. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  206. * are passed an instance of {@link TileProviderError}.
  207. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  208. * @type {Event}
  209. * @readonly
  210. */
  211. errorEvent: {
  212. get: function () {
  213. return this._errorEvent;
  214. },
  215. },
  216. /**
  217. * Gets a value indicating whether or not the provider is ready for use.
  218. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  219. * @type {Boolean}
  220. * @readonly
  221. */
  222. ready: {
  223. get: function () {
  224. return this._ready;
  225. },
  226. },
  227. /**
  228. * Gets a promise that resolves to true when the provider is ready for use.
  229. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  230. * @type {Promise.<Boolean>}
  231. * @readonly
  232. */
  233. readyPromise: {
  234. get: function () {
  235. return this._readyPromise;
  236. },
  237. },
  238. /**
  239. * Gets the credit to display when this terrain provider is active. Typically this is used to credit
  240. * the source of the terrain. This function should not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true.
  241. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  242. * @type {Credit}
  243. * @readonly
  244. */
  245. credit: {
  246. get: function () {
  247. return this._credit;
  248. },
  249. },
  250. /**
  251. * Gets a value indicating whether or not the provider includes a water mask. The water mask
  252. * indicates which areas of the globe are water rather than land, so they can be rendered
  253. * as a reflective surface with animated waves. This function should not be
  254. * called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true.
  255. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  256. * @type {Boolean}
  257. */
  258. hasWaterMask: {
  259. get: function () {
  260. return false;
  261. },
  262. },
  263. /**
  264. * Gets a value indicating whether or not the requested tiles include vertex normals.
  265. * This function should not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true.
  266. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  267. * @type {Boolean}
  268. */
  269. hasVertexNormals: {
  270. get: function () {
  271. return false;
  272. },
  273. },
  274. /**
  275. * Gets an object that can be used to determine availability of terrain from this provider, such as
  276. * at points and in rectangles. This function should not be called before
  277. * {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true. This property may be undefined if availability
  278. * information is not available.
  279. * @memberof GoogleEarthEnterpriseTerrainProvider.prototype
  280. * @type {TileAvailability}
  281. */
  282. availability: {
  283. get: function () {
  284. return undefined;
  285. },
  286. },
  287. });
  288. var taskProcessor = new TaskProcessor(
  289. "decodeGoogleEarthEnterprisePacket",
  290. Number.POSITIVE_INFINITY
  291. );
  292. // If the tile has its own terrain, then you can just use its child bitmask. If it was requested using it's parent
  293. // then you need to check all of its children to see if they have terrain.
  294. function computeChildMask(quadKey, info, metadata) {
  295. var childMask = info.getChildBitmask();
  296. if (info.terrainState === TerrainState.PARENT) {
  297. childMask = 0;
  298. for (var i = 0; i < 4; ++i) {
  299. var child = metadata.getTileInformationFromQuadKey(
  300. quadKey + i.toString()
  301. );
  302. if (defined(child) && child.hasTerrain()) {
  303. childMask |= 1 << i;
  304. }
  305. }
  306. }
  307. return childMask;
  308. }
  309. /**
  310. * Requests the geometry for a given tile. This function should not be called before
  311. * {@link GoogleEarthEnterpriseTerrainProvider#ready} returns true. The result must include terrain data and
  312. * may optionally include a water mask and an indication of which child tiles are available.
  313. *
  314. * @param {Number} x The X coordinate of the tile for which to request geometry.
  315. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  316. * @param {Number} level The level of the tile for which to request geometry.
  317. * @param {Request} [request] The request object. Intended for internal use only.
  318. * @returns {Promise.<TerrainData>|undefined} A promise for the requested geometry. If this method
  319. * returns undefined instead of a promise, it is an indication that too many requests are already
  320. * pending and the request will be retried later.
  321. *
  322. * @exception {DeveloperError} This function must not be called before {@link GoogleEarthEnterpriseTerrainProvider#ready}
  323. * returns true.
  324. */
  325. GoogleEarthEnterpriseTerrainProvider.prototype.requestTileGeometry = function (
  326. x,
  327. y,
  328. level,
  329. request
  330. ) {
  331. //>>includeStart('debug', pragmas.debug)
  332. if (!this._ready) {
  333. throw new DeveloperError(
  334. "requestTileGeometry must not be called before the terrain provider is ready."
  335. );
  336. }
  337. //>>includeEnd('debug');
  338. var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level);
  339. var terrainCache = this._terrainCache;
  340. var metadata = this._metadata;
  341. var info = metadata.getTileInformationFromQuadKey(quadKey);
  342. // Check if this tile is even possibly available
  343. if (!defined(info)) {
  344. return when.reject(new RuntimeError("Terrain tile doesn't exist"));
  345. }
  346. var terrainState = info.terrainState;
  347. if (!defined(terrainState)) {
  348. // First time we have tried to load this tile, so set terrain state to UNKNOWN
  349. terrainState = info.terrainState = TerrainState.UNKNOWN;
  350. }
  351. // If its in the cache, return it
  352. var buffer = terrainCache.get(quadKey);
  353. if (defined(buffer)) {
  354. var credit = metadata.providers[info.terrainProvider];
  355. return when.resolve(
  356. new GoogleEarthEnterpriseTerrainData({
  357. buffer: buffer,
  358. childTileMask: computeChildMask(quadKey, info, metadata),
  359. credits: defined(credit) ? [credit] : undefined,
  360. negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias,
  361. negativeElevationThreshold: metadata.negativeAltitudeThreshold,
  362. })
  363. );
  364. }
  365. // Clean up the cache
  366. terrainCache.tidy();
  367. // We have a tile, check to see if no ancestors have terrain or that we know for sure it doesn't
  368. if (!info.ancestorHasTerrain) {
  369. // We haven't reached a level with terrain, so return the ellipsoid
  370. return when.resolve(
  371. new HeightmapTerrainData({
  372. buffer: new Uint8Array(16 * 16),
  373. width: 16,
  374. height: 16,
  375. })
  376. );
  377. } else if (terrainState === TerrainState.NONE) {
  378. // Already have info and there isn't any terrain here
  379. return when.reject(new RuntimeError("Terrain tile doesn't exist"));
  380. }
  381. // Figure out where we are getting the terrain and what version
  382. var parentInfo;
  383. var q = quadKey;
  384. var terrainVersion = -1;
  385. switch (terrainState) {
  386. case TerrainState.SELF: // We have terrain and have retrieved it before
  387. terrainVersion = info.terrainVersion;
  388. break;
  389. case TerrainState.PARENT: // We have terrain in our parent
  390. q = q.substring(0, q.length - 1);
  391. parentInfo = metadata.getTileInformationFromQuadKey(q);
  392. terrainVersion = parentInfo.terrainVersion;
  393. break;
  394. case TerrainState.UNKNOWN: // We haven't tried to retrieve terrain yet
  395. if (info.hasTerrain()) {
  396. terrainVersion = info.terrainVersion; // We should have terrain
  397. } else {
  398. q = q.substring(0, q.length - 1);
  399. parentInfo = metadata.getTileInformationFromQuadKey(q);
  400. if (defined(parentInfo) && parentInfo.hasTerrain()) {
  401. terrainVersion = parentInfo.terrainVersion; // Try checking in the parent
  402. }
  403. }
  404. break;
  405. }
  406. // We can't figure out where to get the terrain
  407. if (terrainVersion < 0) {
  408. return when.reject(new RuntimeError("Terrain tile doesn't exist"));
  409. }
  410. // Load that terrain
  411. var terrainPromises = this._terrainPromises;
  412. var terrainRequests = this._terrainRequests;
  413. var sharedPromise;
  414. var sharedRequest;
  415. if (defined(terrainPromises[q])) {
  416. // Already being loaded possibly from another child, so return existing promise
  417. sharedPromise = terrainPromises[q];
  418. sharedRequest = terrainRequests[q];
  419. } else {
  420. // Create new request for terrain
  421. sharedRequest = request;
  422. var requestPromise = buildTerrainResource(
  423. this,
  424. q,
  425. terrainVersion,
  426. sharedRequest
  427. ).fetchArrayBuffer();
  428. if (!defined(requestPromise)) {
  429. return undefined; // Throttled
  430. }
  431. sharedPromise = requestPromise.then(function (terrain) {
  432. if (defined(terrain)) {
  433. return taskProcessor
  434. .scheduleTask(
  435. {
  436. buffer: terrain,
  437. type: "Terrain",
  438. key: metadata.key,
  439. },
  440. [terrain]
  441. )
  442. .then(function (terrainTiles) {
  443. // Add requested tile and mark it as SELF
  444. var requestedInfo = metadata.getTileInformationFromQuadKey(q);
  445. requestedInfo.terrainState = TerrainState.SELF;
  446. terrainCache.add(q, terrainTiles[0]);
  447. var provider = requestedInfo.terrainProvider;
  448. // Add children to cache
  449. var count = terrainTiles.length - 1;
  450. for (var j = 0; j < count; ++j) {
  451. var childKey = q + j.toString();
  452. var child = metadata.getTileInformationFromQuadKey(childKey);
  453. if (defined(child)) {
  454. terrainCache.add(childKey, terrainTiles[j + 1]);
  455. child.terrainState = TerrainState.PARENT;
  456. if (child.terrainProvider === 0) {
  457. child.terrainProvider = provider;
  458. }
  459. }
  460. }
  461. });
  462. }
  463. return when.reject(new RuntimeError("Failed to load terrain."));
  464. });
  465. terrainPromises[q] = sharedPromise; // Store promise without delete from terrainPromises
  466. terrainRequests[q] = sharedRequest;
  467. // Set promise so we remove from terrainPromises just one time
  468. sharedPromise = sharedPromise.always(function () {
  469. delete terrainPromises[q];
  470. delete terrainRequests[q];
  471. });
  472. }
  473. return sharedPromise
  474. .then(function () {
  475. var buffer = terrainCache.get(quadKey);
  476. if (defined(buffer)) {
  477. var credit = metadata.providers[info.terrainProvider];
  478. return new GoogleEarthEnterpriseTerrainData({
  479. buffer: buffer,
  480. childTileMask: computeChildMask(quadKey, info, metadata),
  481. credits: defined(credit) ? [credit] : undefined,
  482. negativeAltitudeExponentBias: metadata.negativeAltitudeExponentBias,
  483. negativeElevationThreshold: metadata.negativeAltitudeThreshold,
  484. });
  485. }
  486. return when.reject(new RuntimeError("Failed to load terrain."));
  487. })
  488. .otherwise(function (error) {
  489. if (sharedRequest.state === RequestState.CANCELLED) {
  490. request.state = sharedRequest.state;
  491. return when.reject(error);
  492. }
  493. info.terrainState = TerrainState.NONE;
  494. return when.reject(error);
  495. });
  496. };
  497. /**
  498. * Gets the maximum geometric error allowed in a tile at a given level.
  499. *
  500. * @param {Number} level The tile level for which to get the maximum geometric error.
  501. * @returns {Number} The maximum geometric error.
  502. */
  503. GoogleEarthEnterpriseTerrainProvider.prototype.getLevelMaximumGeometricError = function (
  504. level
  505. ) {
  506. return this._levelZeroMaximumGeometricError / (1 << level);
  507. };
  508. /**
  509. * Determines whether data for a tile is available to be loaded.
  510. *
  511. * @param {Number} x The X coordinate of the tile for which to request geometry.
  512. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  513. * @param {Number} level The level of the tile for which to request geometry.
  514. * @returns {Boolean} Undefined if not supported, otherwise true or false.
  515. */
  516. GoogleEarthEnterpriseTerrainProvider.prototype.getTileDataAvailable = function (
  517. x,
  518. y,
  519. level
  520. ) {
  521. var metadata = this._metadata;
  522. var quadKey = GoogleEarthEnterpriseMetadata.tileXYToQuadKey(x, y, level);
  523. var info = metadata.getTileInformation(x, y, level);
  524. if (info === null) {
  525. return false;
  526. }
  527. if (defined(info)) {
  528. if (!info.ancestorHasTerrain) {
  529. return true; // We'll just return the ellipsoid
  530. }
  531. var terrainState = info.terrainState;
  532. if (terrainState === TerrainState.NONE) {
  533. return false; // Terrain is not available
  534. }
  535. if (!defined(terrainState) || terrainState === TerrainState.UNKNOWN) {
  536. info.terrainState = TerrainState.UNKNOWN;
  537. if (!info.hasTerrain()) {
  538. quadKey = quadKey.substring(0, quadKey.length - 1);
  539. var parentInfo = metadata.getTileInformationFromQuadKey(quadKey);
  540. if (!defined(parentInfo) || !parentInfo.hasTerrain()) {
  541. return false;
  542. }
  543. }
  544. }
  545. return true;
  546. }
  547. if (metadata.isValid(quadKey)) {
  548. // We will need this tile, so request metadata and return false for now
  549. var request = new Request({
  550. throttle: true,
  551. throttleByServer: true,
  552. type: RequestType.TERRAIN,
  553. });
  554. metadata.populateSubtree(x, y, level, request);
  555. }
  556. return false;
  557. };
  558. /**
  559. * Makes sure we load availability data for a tile
  560. *
  561. * @param {Number} x The X coordinate of the tile for which to request geometry.
  562. * @param {Number} y The Y coordinate of the tile for which to request geometry.
  563. * @param {Number} level The level of the tile for which to request geometry.
  564. * @returns {undefined|Promise<void>} Undefined if nothing need to be loaded or a Promise that resolves when all required tiles are loaded
  565. */
  566. GoogleEarthEnterpriseTerrainProvider.prototype.loadTileDataAvailability = function (
  567. x,
  568. y,
  569. level
  570. ) {
  571. return undefined;
  572. };
  573. //
  574. // Functions to handle imagery packets
  575. //
  576. function buildTerrainResource(terrainProvider, quadKey, version, request) {
  577. version = defined(version) && version > 0 ? version : 1;
  578. return terrainProvider._metadata.resource.getDerivedResource({
  579. url: "flatfile?f1c-0" + quadKey + "-t." + version.toString(),
  580. request: request,
  581. });
  582. }
  583. export default GoogleEarthEnterpriseTerrainProvider;