GoogleEarthImageryProvider.js 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /*global define*/
  2. define([
  3. '../Core/Credit',
  4. '../Core/defaultValue',
  5. '../Core/defined',
  6. '../Core/defineProperties',
  7. '../Core/DeveloperError',
  8. '../Core/Event',
  9. '../Core/GeographicTilingScheme',
  10. '../Core/loadText',
  11. '../Core/Rectangle',
  12. '../Core/RuntimeError',
  13. '../Core/TileProviderError',
  14. '../Core/WebMercatorTilingScheme',
  15. '../ThirdParty/when',
  16. './ImageryProvider'
  17. ], function(
  18. Credit,
  19. defaultValue,
  20. defined,
  21. defineProperties,
  22. DeveloperError,
  23. Event,
  24. GeographicTilingScheme,
  25. loadText,
  26. Rectangle,
  27. RuntimeError,
  28. TileProviderError,
  29. WebMercatorTilingScheme,
  30. when,
  31. ImageryProvider) {
  32. "use strict";
  33. /**
  34. * Provides tiled imagery using the Google Earth Imagery API.
  35. *
  36. * Notes: This imagery provider does not work with the public Google Earth servers. It works with the
  37. * Google Earth Enterprise Server.
  38. *
  39. * By default the Google Earth Enterprise server does not set the
  40. * {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing} headers. You can either
  41. * use a proxy server which adds these headers, or in the /opt/google/gehttpd/conf/gehttpd.conf
  42. * and add the 'Header set Access-Control-Allow-Origin "*"' option to the '<Directory />' and
  43. * '<Directory "/opt/google/gehttpd/htdocs">' directives.
  44. *
  45. * @alias GoogleEarthImageryProvider
  46. * @constructor
  47. *
  48. * @param {Object} options Object with the following properties:
  49. * @param {String} options.url The url of the Google Earth server hosting the imagery.
  50. * @param {Number} options.channel The channel (id) to be used when requesting data from the server.
  51. * The channel number can be found by looking at the json file located at:
  52. * earth.localdomain/default_map/query?request=Json&vars=geeServerDefs The /default_map path may
  53. * differ depending on your Google Earth Enterprise server configuration. Look for the "id" that
  54. * is associated with a "ImageryMaps" requestType. There may be more than one id available.
  55. * Example:
  56. * {
  57. * layers: [
  58. * {
  59. * id: 1002,
  60. * requestType: "ImageryMaps"
  61. * },
  62. * {
  63. * id: 1007,
  64. * requestType: "VectorMapsRaster"
  65. * }
  66. * ]
  67. * }
  68. * @param {String} [options.path="/default_map"] The path of the Google Earth server hosting the imagery.
  69. * @param {Number} [options.maximumLevel=23] The maximum level-of-detail supported by the Google Earth
  70. * Enterprise server.
  71. * @param {TileDiscardPolicy} [options.tileDiscardPolicy] The policy that determines if a tile
  72. * is invalid and should be discarded. To ensure that no tiles are discarded, construct and pass
  73. * a {@link NeverTileDiscardPolicy} for this parameter.
  74. * @param {Proxy} [options.proxy] A proxy to use for requests. This object is
  75. * expected to have a getURL function which returns the proxied URL, if needed.
  76. *
  77. * @exception {RuntimeError} Could not find layer with channel (id) of <code>options.channel</code>.
  78. * @exception {RuntimeError} Could not find a version in channel (id) <code>options.channel</code>.
  79. * @exception {RuntimeError} Unsupported projection <code>data.projection</code>.
  80. *
  81. * @see ArcGisMapServerImageryProvider
  82. * @see BingMapsImageryProvider
  83. * @see OpenStreetMapImageryProvider
  84. * @see SingleTileImageryProvider
  85. * @see TileMapServiceImageryProvider
  86. * @see WebMapServiceImageryProvider
  87. *
  88. * @see {@link http://www.w3.org/TR/cors/|Cross-Origin Resource Sharing}
  89. *
  90. * @example
  91. * var google = new Cesium.GoogleEarthImageryProvider({
  92. * url : '//earth.localdomain',
  93. * channel : 1008
  94. * });
  95. */
  96. var GoogleEarthImageryProvider = function GoogleEarthImageryProvider(options) {
  97. options = defaultValue(options, {});
  98. //>>includeStart('debug', pragmas.debug);
  99. if (!defined(options.url)) {
  100. throw new DeveloperError('options.url is required.');
  101. }
  102. if (!defined(options.channel)) {
  103. throw new DeveloperError('options.channel is required.');
  104. }
  105. //>>includeEnd('debug');
  106. this._url = options.url;
  107. this._path = defaultValue(options.path, '/default_map');
  108. this._tileDiscardPolicy = options.tileDiscardPolicy;
  109. this._proxy = options.proxy;
  110. this._channel = options.channel;
  111. this._requestType = 'ImageryMaps';
  112. this._credit = new Credit('Google Imagery', GoogleEarthImageryProvider._logoData, 'http://www.google.com/enterprise/mapsearth/products/earthenterprise.html');
  113. /**
  114. * The default {@link ImageryLayer#gamma} to use for imagery layers created for this provider.
  115. * By default, this is set to 1.9. Changing this value after creating an {@link ImageryLayer} for this provider will have
  116. * no effect. Instead, set the layer's {@link ImageryLayer#gamma} property.
  117. *
  118. * @type {Number}
  119. * @default 1.9
  120. */
  121. this.defaultGamma = 1.9;
  122. this._tilingScheme = undefined;
  123. this._version = undefined;
  124. this._tileWidth = 256;
  125. this._tileHeight = 256;
  126. this._maximumLevel = defaultValue(options.maximumLevel, 23);
  127. this._imageUrlTemplate = this._url + this._path + '/query?request={request}&channel={channel}&version={version}&x={x}&y={y}&z={zoom}';
  128. this._errorEvent = new Event();
  129. this._ready = false;
  130. var metadataUrl = this._url + this._path + '/query?request=Json&vars=geeServerDefs&is2d=t';
  131. var that = this;
  132. var metadataError;
  133. function metadataSuccess(text) {
  134. var data;
  135. // The Google Earth server sends malformed JSON data currently...
  136. try {
  137. // First, try parsing it like normal in case a future version sends correctly formatted JSON
  138. data = JSON.parse(text);
  139. } catch(e) {
  140. // Quote object strings manually, then try parsing again
  141. data = JSON.parse(text.replace(/([\[\{,])[\n\r ]*([A-Za-z0-9]+)[\n\r ]*:/g, '$1"$2":'));
  142. }
  143. var layer;
  144. for(var i = 0; i < data.layers.length; i++) {
  145. if(data.layers[i].id === that._channel) {
  146. layer = data.layers[i];
  147. break;
  148. }
  149. }
  150. var message;
  151. if(!defined(layer)) {
  152. message = 'Could not find layer with channel (id) of ' + that._channel + '.';
  153. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
  154. throw new RuntimeError(message);
  155. }
  156. if(!defined(layer.version)) {
  157. message = 'Could not find a version in channel (id) ' + that._channel + '.';
  158. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
  159. throw new RuntimeError(message);
  160. }
  161. that._version = layer.version;
  162. if(defined(data.projection) && data.projection === 'flat') {
  163. that._tilingScheme = new GeographicTilingScheme({
  164. numberOfLevelZeroTilesX : 2,
  165. numberOfLevelZeroTilesY : 2,
  166. rectangle: new Rectangle(-Math.PI, -Math.PI, Math.PI, Math.PI)
  167. });
  168. // Default to mercator projection when projection is undefined
  169. } else if(!defined(data.projection) || data.projection === 'mercator') {
  170. that._tilingScheme = new WebMercatorTilingScheme({
  171. numberOfLevelZeroTilesX : 2,
  172. numberOfLevelZeroTilesY : 2
  173. });
  174. } else {
  175. message = 'Unsupported projection ' + data.projection + '.';
  176. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
  177. throw new RuntimeError(message);
  178. }
  179. that._imageUrlTemplate = that._imageUrlTemplate.replace('{request}', that._requestType)
  180. .replace('{channel}', that._channel).replace('{version}', that._version);
  181. that._ready = true;
  182. TileProviderError.handleSuccess(metadataError);
  183. }
  184. function metadataFailure(e) {
  185. var message = 'An error occurred while accessing ' + metadataUrl + '.';
  186. metadataError = TileProviderError.handleError(metadataError, that, that._errorEvent, message, undefined, undefined, undefined, requestMetadata);
  187. }
  188. function requestMetadata() {
  189. var url = (!defined(that._proxy)) ? metadataUrl : that._proxy.getURL(metadataUrl);
  190. var metadata = loadText(url);
  191. when(metadata, metadataSuccess, metadataFailure);
  192. }
  193. requestMetadata();
  194. };
  195. defineProperties(GoogleEarthImageryProvider.prototype, {
  196. /**
  197. * Gets the URL of the Google Earth MapServer.
  198. * @memberof GoogleEarthImageryProvider.prototype
  199. * @type {String}
  200. * @readonly
  201. */
  202. url : {
  203. get : function() {
  204. return this._url;
  205. }
  206. },
  207. /**
  208. * Gets the url path of the data on the Google Earth server.
  209. * @memberof GoogleEarthImageryProvider.prototype
  210. * @type {String}
  211. * @readonly
  212. */
  213. path : {
  214. get : function() {
  215. return this._path;
  216. }
  217. },
  218. /**
  219. * Gets the proxy used by this provider.
  220. * @memberof GoogleEarthImageryProvider.prototype
  221. * @type {Proxy}
  222. * @readonly
  223. */
  224. proxy : {
  225. get : function() {
  226. return this._proxy;
  227. }
  228. },
  229. /**
  230. * Gets the imagery channel (id) currently being used.
  231. * @memberof GoogleEarthImageryProvider.prototype
  232. * @type {Number}
  233. * @readonly
  234. */
  235. channel : {
  236. get : function() {
  237. return this._channel;
  238. }
  239. },
  240. /**
  241. * Gets the width of each tile, in pixels. This function should
  242. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  243. * @memberof GoogleEarthImageryProvider.prototype
  244. * @type {Number}
  245. * @readonly
  246. */
  247. tileWidth : {
  248. get : function() {
  249. //>>includeStart('debug', pragmas.debug);
  250. if (!this._ready) {
  251. throw new DeveloperError('tileWidth must not be called before the imagery provider is ready.');
  252. }
  253. //>>includeEnd('debug');
  254. return this._tileWidth;
  255. }
  256. },
  257. /**
  258. * Gets the height of each tile, in pixels. This function should
  259. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  260. * @memberof GoogleEarthImageryProvider.prototype
  261. * @type {Number}
  262. * @readonly
  263. */
  264. tileHeight: {
  265. get : function() {
  266. //>>includeStart('debug', pragmas.debug);
  267. if (!this._ready) {
  268. throw new DeveloperError('tileHeight must not be called before the imagery provider is ready.');
  269. }
  270. //>>includeEnd('debug');
  271. return this._tileHeight;
  272. }
  273. },
  274. /**
  275. * Gets the maximum level-of-detail that can be requested. This function should
  276. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  277. * @memberof GoogleEarthImageryProvider.prototype
  278. * @type {Number}
  279. * @readonly
  280. */
  281. maximumLevel : {
  282. get : function() {
  283. //>>includeStart('debug', pragmas.debug);
  284. if (!this._ready) {
  285. throw new DeveloperError('maximumLevel must not be called before the imagery provider is ready.');
  286. }
  287. //>>includeEnd('debug');
  288. return this._maximumLevel;
  289. }
  290. },
  291. /**
  292. * Gets the minimum level-of-detail that can be requested. This function should
  293. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  294. * @memberof GoogleEarthImageryProvider.prototype
  295. * @type {Number}
  296. * @readonly
  297. */
  298. minimumLevel : {
  299. get : function() {
  300. //>>includeStart('debug', pragmas.debug);
  301. if (!this._ready) {
  302. throw new DeveloperError('minimumLevel must not be called before the imagery provider is ready.');
  303. }
  304. //>>includeEnd('debug');
  305. return 0;
  306. }
  307. },
  308. /**
  309. * Gets the tiling scheme used by this provider. This function should
  310. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  311. * @memberof GoogleEarthImageryProvider.prototype
  312. * @type {TilingScheme}
  313. * @readonly
  314. */
  315. tilingScheme : {
  316. get : function() {
  317. //>>includeStart('debug', pragmas.debug);
  318. if (!this._ready) {
  319. throw new DeveloperError('tilingScheme must not be called before the imagery provider is ready.');
  320. }
  321. //>>includeEnd('debug');
  322. return this._tilingScheme;
  323. }
  324. },
  325. /**
  326. * Gets the version of the data used by this provider. This function should
  327. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  328. * @memberof GoogleEarthImageryProvider.prototype
  329. * @type {Number}
  330. * @readonly
  331. */
  332. version : {
  333. get : function() {
  334. //>>includeStart('debug', pragmas.debug);
  335. if (!this._ready) {
  336. throw new DeveloperError('version must not be called before the imagery provider is ready.');
  337. }
  338. //>>includeEnd('debug');
  339. return this._version;
  340. }
  341. },
  342. /**
  343. * Gets the type of data that is being requested from the provider. This function should
  344. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  345. * @memberof GoogleEarthImageryProvider.prototype
  346. * @type {String}
  347. * @readonly
  348. */
  349. requestType : {
  350. get : function() {
  351. //>>includeStart('debug', pragmas.debug);
  352. if (!this._ready) {
  353. throw new DeveloperError('requestType must not be called before the imagery provider is ready.');
  354. }
  355. //>>includeEnd('debug');
  356. return this._requestType;
  357. }
  358. },
  359. /**
  360. * Gets the rectangle, in radians, of the imagery provided by this instance. This function should
  361. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  362. * @memberof GoogleEarthImageryProvider.prototype
  363. * @type {Rectangle}
  364. * @readonly
  365. */
  366. rectangle : {
  367. get : function() {
  368. //>>includeStart('debug', pragmas.debug);
  369. if (!this._ready) {
  370. throw new DeveloperError('rectangle must not be called before the imagery provider is ready.');
  371. }
  372. //>>includeEnd('debug');
  373. return this._tilingScheme.rectangle;
  374. }
  375. },
  376. /**
  377. * Gets the tile discard policy. If not undefined, the discard policy is responsible
  378. * for filtering out "missing" tiles via its shouldDiscardImage function. If this function
  379. * returns undefined, no tiles are filtered. This function should
  380. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  381. * @memberof GoogleEarthImageryProvider.prototype
  382. * @type {TileDiscardPolicy}
  383. * @readonly
  384. */
  385. tileDiscardPolicy : {
  386. get : function() {
  387. //>>includeStart('debug', pragmas.debug);
  388. if (!this._ready) {
  389. throw new DeveloperError('tileDiscardPolicy must not be called before the imagery provider is ready.');
  390. }
  391. //>>includeEnd('debug');
  392. return this._tileDiscardPolicy;
  393. }
  394. },
  395. /**
  396. * Gets an event that is raised when the imagery provider encounters an asynchronous error. By subscribing
  397. * to the event, you will be notified of the error and can potentially recover from it. Event listeners
  398. * are passed an instance of {@link TileProviderError}.
  399. * @memberof GoogleEarthImageryProvider.prototype
  400. * @type {Event}
  401. * @readonly
  402. */
  403. errorEvent : {
  404. get : function() {
  405. return this._errorEvent;
  406. }
  407. },
  408. /**
  409. * Gets a value indicating whether or not the provider is ready for use.
  410. * @memberof GoogleEarthImageryProvider.prototype
  411. * @type {Boolean}
  412. * @readonly
  413. */
  414. ready : {
  415. get : function() {
  416. return this._ready;
  417. }
  418. },
  419. /**
  420. * Gets the credit to display when this imagery provider is active. Typically this is used to credit
  421. * the source of the imagery. This function should not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  422. * @memberof GoogleEarthImageryProvider.prototype
  423. * @type {Credit}
  424. * @readonly
  425. */
  426. credit : {
  427. get : function() {
  428. return this._credit;
  429. }
  430. },
  431. /**
  432. * Gets a value indicating whether or not the images provided by this imagery provider
  433. * include an alpha channel. If this property is false, an alpha channel, if present, will
  434. * be ignored. If this property is true, any images without an alpha channel will be treated
  435. * as if their alpha is 1.0 everywhere. When this property is false, memory usage
  436. * and texture upload time are reduced.
  437. * @memberof GoogleEarthImageryProvider.prototype
  438. * @type {Boolean}
  439. * @readonly
  440. */
  441. hasAlphaChannel : {
  442. get : function() {
  443. return true;
  444. }
  445. }
  446. });
  447. /**
  448. * Gets the credits to be displayed when a given tile is displayed.
  449. *
  450. * @param {Number} x The tile X coordinate.
  451. * @param {Number} y The tile Y coordinate.
  452. * @param {Number} level The tile level;
  453. * @returns {Credit[]} The credits to be displayed when the tile is displayed.
  454. *
  455. * @exception {DeveloperError} <code>getTileCredits</code> must not be called before the imagery provider is ready.
  456. */
  457. GoogleEarthImageryProvider.prototype.getTileCredits = function(x, y, level) {
  458. return undefined;
  459. };
  460. /**
  461. * Requests the image for a given tile. This function should
  462. * not be called before {@link GoogleEarthImageryProvider#ready} returns true.
  463. *
  464. * @param {Number} x The tile X coordinate.
  465. * @param {Number} y The tile Y coordinate.
  466. * @param {Number} level The tile level.
  467. * @returns {Promise} A promise for the image that will resolve when the image is available, or
  468. * undefined if there are too many active requests to the server, and the request
  469. * should be retried later. The resolved image may be either an
  470. * Image or a Canvas DOM object.
  471. *
  472. * @exception {DeveloperError} <code>requestImage</code> must not be called before the imagery provider is ready.
  473. */
  474. GoogleEarthImageryProvider.prototype.requestImage = function(x, y, level) {
  475. //>>includeStart('debug', pragmas.debug);
  476. if (!this._ready) {
  477. throw new DeveloperError('requestImage must not be called before the imagery provider is ready.');
  478. }
  479. //>>includeEnd('debug');
  480. var url = buildImageUrl(this, x, y, level);
  481. return ImageryProvider.loadImage(this, url);
  482. };
  483. /**
  484. * Picking features is not currently supported by this imagery provider, so this function simply returns
  485. * undefined.
  486. *
  487. * @param {Number} x The tile X coordinate.
  488. * @param {Number} y The tile Y coordinate.
  489. * @param {Number} level The tile level.
  490. * @param {Number} longitude The longitude at which to pick features.
  491. * @param {Number} latitude The latitude at which to pick features.
  492. * @return {Promise} A promise for the picked features that will resolve when the asynchronous
  493. * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
  494. * instances. The array may be empty if no features are found at the given location.
  495. * It may also be undefined if picking is not supported.
  496. */
  497. GoogleEarthImageryProvider.prototype.pickFeatures = function() {
  498. return undefined;
  499. };
  500. GoogleEarthImageryProvider._logoData = 'data:image/png;base64,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';
  501. function buildImageUrl(imageryProvider, x, y, level) {
  502. var imageUrl = imageryProvider._imageUrlTemplate;
  503. imageUrl = imageUrl.replace('{x}', x);
  504. imageUrl = imageUrl.replace('{y}', y);
  505. // Google Earth starts with a zoom level of 1, not 0
  506. imageUrl = imageUrl.replace('{zoom}', (level + 1));
  507. var proxy = imageryProvider._proxy;
  508. if (defined(proxy)) {
  509. imageUrl = proxy.getURL(imageUrl);
  510. }
  511. return imageUrl;
  512. }
  513. return GoogleEarthImageryProvider;
  514. });