GoogleEarthEnterpriseTerrainProvider.js 24 KB

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