BaseLayerPickerViewModel.js 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. import {
  2. defaultValue,
  3. defined,
  4. DeveloperError,
  5. EllipsoidTerrainProvider,
  6. ImageryLayer,
  7. Terrain,
  8. } from "@cesium/engine";
  9. import knockout from "../ThirdParty/knockout.js";
  10. import createCommand from "../createCommand.js";
  11. /**
  12. * The view model for {@link BaseLayerPicker}.
  13. * @alias BaseLayerPickerViewModel
  14. * @constructor
  15. *
  16. * @param {object} options Object with the following properties:
  17. * @param {Globe} options.globe The Globe to use.
  18. * @param {ProviderViewModel[]} [options.imageryProviderViewModels=[]] The array of ProviderViewModel instances to use for imagery.
  19. * @param {ProviderViewModel} [options.selectedImageryProviderViewModel] The view model for the current base imagery layer, if not supplied the first available imagery layer is used.
  20. * @param {ProviderViewModel[]} [options.terrainProviderViewModels=[]] The array of ProviderViewModel instances to use for terrain.
  21. * @param {ProviderViewModel} [options.selectedTerrainProviderViewModel] The view model for the current base terrain layer, if not supplied the first available terrain layer is used.
  22. *
  23. * @exception {DeveloperError} imageryProviderViewModels must be an array.
  24. * @exception {DeveloperError} terrainProviderViewModels must be an array.
  25. */
  26. function BaseLayerPickerViewModel(options) {
  27. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  28. const globe = options.globe;
  29. const imageryProviderViewModels = defaultValue(
  30. options.imageryProviderViewModels,
  31. []
  32. );
  33. const terrainProviderViewModels = defaultValue(
  34. options.terrainProviderViewModels,
  35. []
  36. );
  37. //>>includeStart('debug', pragmas.debug);
  38. if (!defined(globe)) {
  39. throw new DeveloperError("globe is required");
  40. }
  41. //>>includeEnd('debug');
  42. this._globe = globe;
  43. /**
  44. * Gets or sets an array of ProviderViewModel instances available for imagery selection.
  45. * This property is observable.
  46. * @type {ProviderViewModel[]}
  47. */
  48. this.imageryProviderViewModels = imageryProviderViewModels.slice(0);
  49. /**
  50. * Gets or sets an array of ProviderViewModel instances available for terrain selection.
  51. * This property is observable.
  52. * @type {ProviderViewModel[]}
  53. */
  54. this.terrainProviderViewModels = terrainProviderViewModels.slice(0);
  55. /**
  56. * Gets or sets whether the imagery selection drop-down is currently visible.
  57. * @type {boolean}
  58. * @default false
  59. */
  60. this.dropDownVisible = false;
  61. knockout.track(this, [
  62. "imageryProviderViewModels",
  63. "terrainProviderViewModels",
  64. "dropDownVisible",
  65. ]);
  66. const imageryObservable = knockout.getObservable(
  67. this,
  68. "imageryProviderViewModels"
  69. );
  70. const imageryProviders = knockout.pureComputed(function () {
  71. const providers = imageryObservable();
  72. const categories = {};
  73. let i;
  74. for (i = 0; i < providers.length; i++) {
  75. const provider = providers[i];
  76. const category = provider.category;
  77. if (defined(categories[category])) {
  78. categories[category].push(provider);
  79. } else {
  80. categories[category] = [provider];
  81. }
  82. }
  83. const allCategoryNames = Object.keys(categories);
  84. const result = [];
  85. for (i = 0; i < allCategoryNames.length; i++) {
  86. const name = allCategoryNames[i];
  87. result.push({
  88. name: name,
  89. providers: categories[name],
  90. });
  91. }
  92. return result;
  93. });
  94. this._imageryProviders = imageryProviders;
  95. const terrainObservable = knockout.getObservable(
  96. this,
  97. "terrainProviderViewModels"
  98. );
  99. const terrainProviders = knockout.pureComputed(function () {
  100. const providers = terrainObservable();
  101. const categories = {};
  102. let i;
  103. for (i = 0; i < providers.length; i++) {
  104. const provider = providers[i];
  105. const category = provider.category;
  106. if (defined(categories[category])) {
  107. categories[category].push(provider);
  108. } else {
  109. categories[category] = [provider];
  110. }
  111. }
  112. const allCategoryNames = Object.keys(categories);
  113. const result = [];
  114. for (i = 0; i < allCategoryNames.length; i++) {
  115. const name = allCategoryNames[i];
  116. result.push({
  117. name: name,
  118. providers: categories[name],
  119. });
  120. }
  121. return result;
  122. });
  123. this._terrainProviders = terrainProviders;
  124. /**
  125. * Gets the button tooltip. This property is observable.
  126. * @type {string}
  127. */
  128. this.buttonTooltip = undefined;
  129. knockout.defineProperty(this, "buttonTooltip", function () {
  130. const selectedImagery = this.selectedImagery;
  131. const selectedTerrain = this.selectedTerrain;
  132. const imageryTip = defined(selectedImagery)
  133. ? selectedImagery.name
  134. : undefined;
  135. const terrainTip = defined(selectedTerrain)
  136. ? selectedTerrain.name
  137. : undefined;
  138. if (defined(imageryTip) && defined(terrainTip)) {
  139. return `${imageryTip}\n${terrainTip}`;
  140. } else if (defined(imageryTip)) {
  141. return imageryTip;
  142. }
  143. return terrainTip;
  144. });
  145. /**
  146. * Gets the button background image. This property is observable.
  147. * @type {string}
  148. */
  149. this.buttonImageUrl = undefined;
  150. knockout.defineProperty(this, "buttonImageUrl", function () {
  151. const selectedImagery = this.selectedImagery;
  152. if (defined(selectedImagery)) {
  153. return selectedImagery.iconUrl;
  154. }
  155. });
  156. /**
  157. * Gets or sets the currently selected imagery. This property is observable.
  158. * @type {ProviderViewModel}
  159. * @default undefined
  160. */
  161. this.selectedImagery = undefined;
  162. const selectedImageryViewModel = knockout.observable();
  163. this._currentImageryLayers = [];
  164. knockout.defineProperty(this, "selectedImagery", {
  165. get: function () {
  166. return selectedImageryViewModel();
  167. },
  168. set: function (value) {
  169. if (selectedImageryViewModel() === value) {
  170. this.dropDownVisible = false;
  171. return;
  172. }
  173. let i;
  174. const currentImageryLayers = this._currentImageryLayers;
  175. const currentImageryLayersLength = currentImageryLayers.length;
  176. const imageryLayers = this._globe.imageryLayers;
  177. let hadExistingBaseLayer = false;
  178. for (i = 0; i < currentImageryLayersLength; i++) {
  179. const layersLength = imageryLayers.length;
  180. for (let x = 0; x < layersLength; x++) {
  181. const layer = imageryLayers.get(x);
  182. if (layer === currentImageryLayers[i]) {
  183. imageryLayers.remove(layer);
  184. hadExistingBaseLayer = true;
  185. break;
  186. }
  187. }
  188. }
  189. if (defined(value)) {
  190. const newProviders = value.creationCommand();
  191. if (Array.isArray(newProviders)) {
  192. const newProvidersLength = newProviders.length;
  193. this._currentImageryLayers = [];
  194. for (i = newProvidersLength - 1; i >= 0; i--) {
  195. const layer = ImageryLayer.fromProviderAsync(newProviders[i]);
  196. imageryLayers.add(layer, 0);
  197. this._currentImageryLayers.push(layer);
  198. }
  199. } else {
  200. this._currentImageryLayers = [];
  201. const layer = ImageryLayer.fromProviderAsync(newProviders);
  202. layer.name = value.name;
  203. if (hadExistingBaseLayer) {
  204. imageryLayers.add(layer, 0);
  205. } else {
  206. const baseLayer = imageryLayers.get(0);
  207. if (defined(baseLayer)) {
  208. imageryLayers.remove(baseLayer);
  209. }
  210. imageryLayers.add(layer, 0);
  211. }
  212. this._currentImageryLayers.push(layer);
  213. }
  214. }
  215. selectedImageryViewModel(value);
  216. this.dropDownVisible = false;
  217. },
  218. });
  219. /**
  220. * Gets or sets the currently selected terrain. This property is observable.
  221. * @type {ProviderViewModel}
  222. * @default undefined
  223. */
  224. this.selectedTerrain = undefined;
  225. const selectedTerrainViewModel = knockout.observable();
  226. knockout.defineProperty(this, "selectedTerrain", {
  227. get: function () {
  228. return selectedTerrainViewModel();
  229. },
  230. set: function (value) {
  231. if (selectedTerrainViewModel() === value) {
  232. this.dropDownVisible = false;
  233. return;
  234. }
  235. let newProvider;
  236. if (defined(value)) {
  237. newProvider = value.creationCommand();
  238. }
  239. let cancelUpdate = false;
  240. const removeCancelListener = this._globe.terrainProviderChanged.addEventListener(
  241. () => {
  242. cancelUpdate = true;
  243. removeCancelListener();
  244. }
  245. );
  246. const terrain = new Terrain(Promise.resolve(newProvider));
  247. const removeEventListener = terrain.readyEvent.addEventListener(
  248. (terrainProvider) => {
  249. if (cancelUpdate) {
  250. // Early return in case something has outside of the picker.
  251. return;
  252. }
  253. this._globe.depthTestAgainstTerrain = !(
  254. terrainProvider instanceof EllipsoidTerrainProvider
  255. );
  256. this._globe.terrainProvider = terrainProvider;
  257. removeEventListener();
  258. }
  259. );
  260. selectedTerrainViewModel(value);
  261. this.dropDownVisible = false;
  262. },
  263. });
  264. const that = this;
  265. this._toggleDropDown = createCommand(function () {
  266. that.dropDownVisible = !that.dropDownVisible;
  267. });
  268. this.selectedImagery = defaultValue(
  269. options.selectedImageryProviderViewModel,
  270. imageryProviderViewModels[0]
  271. );
  272. this.selectedTerrain = defaultValue(
  273. options.selectedTerrainProviderViewModel,
  274. terrainProviderViewModels[0]
  275. );
  276. }
  277. Object.defineProperties(BaseLayerPickerViewModel.prototype, {
  278. /**
  279. * Gets the command to toggle the visibility of the drop down.
  280. * @memberof BaseLayerPickerViewModel.prototype
  281. *
  282. * @type {Command}
  283. */
  284. toggleDropDown: {
  285. get: function () {
  286. return this._toggleDropDown;
  287. },
  288. },
  289. /**
  290. * Gets the globe.
  291. * @memberof BaseLayerPickerViewModel.prototype
  292. *
  293. * @type {Globe}
  294. */
  295. globe: {
  296. get: function () {
  297. return this._globe;
  298. },
  299. },
  300. });
  301. export default BaseLayerPickerViewModel;