I3SLayer.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. import defined from "../Core/defined.js";
  2. import Rectangle from "../Core/Rectangle.js";
  3. import Resource from "../Core/Resource.js";
  4. import RuntimeError from "../Core/RuntimeError.js";
  5. import Cesium3DTileset from "./Cesium3DTileset.js";
  6. import I3SNode from "./I3SNode.js";
  7. /**
  8. * This class implements an I3S layer. In CesiumJS each I3SLayer creates a Cesium3DTileset.
  9. * <p>
  10. * Do not construct this directly, instead access layers through {@link I3SDataProvider}.
  11. * </p>
  12. * @alias I3SLayer
  13. * @internalConstructor
  14. * @privateParam {I3SDataProvider} dataProvider The i3s data provider
  15. * @privateParam {object} layerData The layer data that is loaded from the scene layer
  16. * @privateParam {number} index The index of the layer to be reflected
  17. */
  18. function I3SLayer(dataProvider, layerData, index) {
  19. this._dataProvider = dataProvider;
  20. if (!defined(layerData.href)) {
  21. // assign a default layer
  22. layerData.href = `./layers/${index}`;
  23. }
  24. const dataProviderUrl = this._dataProvider.resource.getUrlComponent();
  25. let tilesetUrl = "";
  26. if (dataProviderUrl.match(/layers\/\d/)) {
  27. tilesetUrl = `${dataProviderUrl}`.replace(/\/+$/, "");
  28. } else {
  29. // Add '/' to url if needed + `${layerData.href}` if tilesetUrl not already in ../layers/[id] format
  30. tilesetUrl = `${dataProviderUrl}`
  31. .replace(/\/?$/, "/")
  32. .concat(`${layerData.href}`);
  33. }
  34. this._version = layerData.store.version;
  35. const splitVersion = this._version.split(".");
  36. this._majorVersion = parseInt(splitVersion[0]);
  37. this._minorVersion = splitVersion.length > 1 ? parseInt(splitVersion[1]) : 0;
  38. this._resource = new Resource({ url: tilesetUrl });
  39. this._resource.setQueryParameters(
  40. this._dataProvider.resource.queryParameters
  41. );
  42. this._resource.appendForwardSlash();
  43. this._data = layerData;
  44. this._rootNode = undefined;
  45. this._nodePages = {};
  46. this._nodePageFetches = {};
  47. this._extent = undefined;
  48. this._tileset = undefined;
  49. this._geometryDefinitions = undefined;
  50. this._computeGeometryDefinitions(true);
  51. this._computeExtent();
  52. }
  53. Object.defineProperties(I3SLayer.prototype, {
  54. /**
  55. * Gets the resource for the layer.
  56. * @memberof I3SLayer.prototype
  57. * @type {Resource}
  58. * @readonly
  59. */
  60. resource: {
  61. get: function () {
  62. return this._resource;
  63. },
  64. },
  65. /**
  66. * Gets the root node of this layer.
  67. * @memberof I3SLayer.prototype
  68. * @type {I3SNode}
  69. * @readonly
  70. */
  71. rootNode: {
  72. get: function () {
  73. return this._rootNode;
  74. },
  75. },
  76. /**
  77. * Gets the Cesium3DTileset for this layer.
  78. * @memberof I3SLayer.prototype
  79. * @type {Cesium3DTileset|undefined}
  80. * @readonly
  81. */
  82. tileset: {
  83. get: function () {
  84. return this._tileset;
  85. },
  86. },
  87. /**
  88. * Gets the I3S data for this object.
  89. * @memberof I3SLayer.prototype
  90. * @type {object}
  91. * @readonly
  92. */
  93. data: {
  94. get: function () {
  95. return this._data;
  96. },
  97. },
  98. /**
  99. * The version string of the loaded I3S dataset
  100. * @memberof I3SLayer.prototype
  101. * @type {string}
  102. * @readonly
  103. */
  104. version: {
  105. get: function () {
  106. return this._version;
  107. },
  108. },
  109. /**
  110. * The major version number of the loaded I3S dataset
  111. * @memberof I3SLayer.prototype
  112. * @type {number}
  113. * @readonly
  114. */
  115. majorVersion: {
  116. get: function () {
  117. return this._majorVersion;
  118. },
  119. },
  120. /**
  121. * The minor version number of the loaded I3S dataset
  122. * @memberof I3SLayer.prototype
  123. * @type {number}
  124. * @readonly
  125. */
  126. minorVersion: {
  127. get: function () {
  128. return this._minorVersion;
  129. },
  130. },
  131. /**
  132. * When <code>true</code>, when the loaded I3S version is 1.6 or older
  133. * @memberof I3SLayer.prototype
  134. * @type {boolean}
  135. * @readonly
  136. */
  137. legacyVersion16: {
  138. get: function () {
  139. if (!defined(this.version)) {
  140. return undefined;
  141. }
  142. if (
  143. this.majorVersion < 1 ||
  144. (this.majorVersion === 1 && this.minorVersion <= 6)
  145. ) {
  146. return true;
  147. }
  148. return false;
  149. },
  150. },
  151. });
  152. /**
  153. * Loads the content, including the root node definition and its children
  154. * @returns {Promise} A promise that is resolved when the layer data is loaded
  155. * @private
  156. */
  157. I3SLayer.prototype.load = async function () {
  158. if (this._data.spatialReference.wkid !== 4326) {
  159. throw new RuntimeError(
  160. `Unsupported spatial reference: ${this._data.spatialReference.wkid}`
  161. );
  162. }
  163. await this._dataProvider.loadGeoidData();
  164. await this._loadRootNode();
  165. await this._create3DTileset();
  166. this._rootNode._tile = this._tileset._root;
  167. this._tileset._root._i3sNode = this._rootNode;
  168. if (this.legacyVersion16) {
  169. return this._rootNode._loadChildren();
  170. }
  171. };
  172. /**
  173. * @private
  174. */
  175. I3SLayer.prototype._computeGeometryDefinitions = function (useCompression) {
  176. // create a table of all geometry buffers based on
  177. // the number of attributes and whether they are
  178. // compressed or not, sort them by priority
  179. this._geometryDefinitions = [];
  180. if (defined(this._data.geometryDefinitions)) {
  181. for (
  182. let defIndex = 0;
  183. defIndex < this._data.geometryDefinitions.length;
  184. defIndex++
  185. ) {
  186. const geometryBuffersInfo = [];
  187. const geometryBuffers = this._data.geometryDefinitions[defIndex]
  188. .geometryBuffers;
  189. for (let bufIndex = 0; bufIndex < geometryBuffers.length; bufIndex++) {
  190. const geometryBuffer = geometryBuffers[bufIndex];
  191. const collectedAttributes = [];
  192. let compressed = false;
  193. if (defined(geometryBuffer.compressedAttributes) && useCompression) {
  194. // check if compressed
  195. compressed = true;
  196. const attributes = geometryBuffer.compressedAttributes.attributes;
  197. for (let i = 0; i < attributes.length; i++) {
  198. collectedAttributes.push(attributes[i]);
  199. }
  200. } else {
  201. // uncompressed attributes
  202. for (const attribute in geometryBuffer) {
  203. if (attribute !== "offset") {
  204. collectedAttributes.push(attribute);
  205. }
  206. }
  207. }
  208. geometryBuffersInfo.push({
  209. compressed: compressed,
  210. attributes: collectedAttributes,
  211. index: geometryBuffers.indexOf(geometryBuffer),
  212. });
  213. }
  214. // rank the buffer info
  215. geometryBuffersInfo.sort(function (a, b) {
  216. if (a.compressed && !b.compressed) {
  217. return -1;
  218. } else if (!a.compressed && b.compressed) {
  219. return 1;
  220. }
  221. return a.attributes.length - b.attributes.length;
  222. });
  223. this._geometryDefinitions.push(geometryBuffersInfo);
  224. }
  225. }
  226. };
  227. /**
  228. * @private
  229. */
  230. I3SLayer.prototype._findBestGeometryBuffers = function (
  231. definition,
  232. attributes
  233. ) {
  234. // find the most appropriate geometry definition
  235. // based on the required attributes, and by favouring
  236. // compression to improve bandwidth requirements
  237. const geometryDefinition = this._geometryDefinitions[definition];
  238. if (defined(geometryDefinition)) {
  239. for (let index = 0; index < geometryDefinition.length; ++index) {
  240. const geometryBufferInfo = geometryDefinition[index];
  241. let missed = false;
  242. const geometryAttributes = geometryBufferInfo.attributes;
  243. for (let attrIndex = 0; attrIndex < attributes.length; attrIndex++) {
  244. if (!geometryAttributes.includes(attributes[attrIndex])) {
  245. missed = true;
  246. break;
  247. }
  248. }
  249. if (!missed) {
  250. return {
  251. bufferIndex: geometryBufferInfo.index,
  252. definition: geometryDefinition,
  253. geometryBufferInfo: geometryBufferInfo,
  254. };
  255. }
  256. }
  257. }
  258. return 0;
  259. };
  260. /**
  261. * @private
  262. */
  263. I3SLayer.prototype._loadRootNode = function () {
  264. if (defined(this._data.nodePages)) {
  265. let rootIndex = 0;
  266. if (defined(this._data.nodePages.rootIndex)) {
  267. rootIndex = this._data.nodePages.rootIndex;
  268. }
  269. this._rootNode = new I3SNode(this, rootIndex, true);
  270. } else {
  271. this._rootNode = new I3SNode(this, this._data.store.rootNode, true);
  272. }
  273. return this._rootNode.load();
  274. };
  275. /**
  276. * @private
  277. */
  278. I3SLayer.prototype._getNodeInNodePages = function (nodeIndex) {
  279. const index = Math.floor(nodeIndex / this._data.nodePages.nodesPerPage);
  280. const offsetInPage = nodeIndex % this._data.nodePages.nodesPerPage;
  281. const that = this;
  282. return this._loadNodePage(index).then(function () {
  283. return that._nodePages[index][offsetInPage];
  284. });
  285. };
  286. /**
  287. * @private
  288. */
  289. I3SLayer._fetchJson = function (resource) {
  290. return resource.fetchJson();
  291. };
  292. /**
  293. * @private
  294. */
  295. I3SLayer.prototype._loadNodePage = function (page) {
  296. const that = this;
  297. // If node page was already requested return the same promise
  298. if (!defined(this._nodePageFetches[page])) {
  299. const nodePageResource = this.resource.getDerivedResource({
  300. url: `nodepages/${page}/`,
  301. });
  302. const fetchPromise = I3SLayer._fetchJson(nodePageResource).then(function (
  303. data
  304. ) {
  305. if (defined(data.error) && data.error.code !== 200) {
  306. return Promise.reject(data.error);
  307. }
  308. that._nodePages[page] = data.nodes;
  309. return data;
  310. });
  311. this._nodePageFetches[page] = fetchPromise;
  312. }
  313. return this._nodePageFetches[page];
  314. };
  315. /**
  316. * @private
  317. */
  318. I3SLayer.prototype._computeExtent = function () {
  319. if (defined(this._data.fullExtent)) {
  320. this._extent = Rectangle.fromDegrees(
  321. this._data.fullExtent.xmin,
  322. this._data.fullExtent.ymin,
  323. this._data.fullExtent.xmax,
  324. this._data.fullExtent.ymax
  325. );
  326. } else if (defined(this._data.store.extent)) {
  327. this._extent = Rectangle.fromDegrees(
  328. this._data.store.extent[0],
  329. this._data.store.extent[1],
  330. this._data.store.extent[2],
  331. this._data.store.extent[3]
  332. );
  333. }
  334. };
  335. /**
  336. * @private
  337. */
  338. I3SLayer.prototype._create3DTileset = async function () {
  339. const inPlaceTileset = {
  340. asset: {
  341. version: "1.0",
  342. },
  343. geometricError: Number.MAX_VALUE,
  344. root: this._rootNode._create3DTileDefinition(),
  345. };
  346. const tilesetBlob = new Blob([JSON.stringify(inPlaceTileset)], {
  347. type: "application/json",
  348. });
  349. const tilesetUrl = URL.createObjectURL(tilesetBlob);
  350. const tilesetOptions = {};
  351. if (defined(this._dataProvider._cesium3dTilesetOptions)) {
  352. for (const x in this._dataProvider._cesium3dTilesetOptions) {
  353. if (this._dataProvider._cesium3dTilesetOptions.hasOwnProperty(x)) {
  354. tilesetOptions[x] = this._dataProvider._cesium3dTilesetOptions[x];
  355. }
  356. }
  357. }
  358. this._tileset = await Cesium3DTileset.fromUrl(tilesetUrl, tilesetOptions);
  359. this._tileset.show = this._dataProvider.show;
  360. this._tileset._isI3STileSet = true;
  361. this._tileset.tileUnload.addEventListener(function (tile) {
  362. tile._i3sNode._clearGeometryData();
  363. URL.revokeObjectURL(tile._contentResource._url);
  364. tile._contentResource = tile._i3sNode.resource;
  365. });
  366. this._tileset.tileVisible.addEventListener(function (tile) {
  367. if (defined(tile._i3sNode)) {
  368. tile._i3sNode._loadChildren();
  369. }
  370. });
  371. };
  372. export default I3SLayer;