智慧水务管理系统 - 精河县供水工程综合管理平台

UrlTemplate3DTilesDataProvider.js 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  1. // @ts-check
  2. import Cartographic from "../Core/Cartographic.js";
  3. import Cesium3DTileset from "./Cesium3DTileset.js";
  4. import DeveloperError from "../Core/DeveloperError.js";
  5. import Ellipsoid from "../Core/Ellipsoid.js";
  6. import Rectangle from "../Core/Rectangle.js";
  7. import Resource from "../Core/Resource.js";
  8. import getAbsoluteUri from "../Core/getAbsoluteUri.js";
  9. import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
  10. import defined from "../Core/defined.js";
  11. import destroyObject from "../Core/destroyObject.js";
  12. import CesiumMath from "../Core/Math.js";
  13. /** @import FrameState from "./FrameState.js"; */
  14. /** @import PassState from "../Renderer/PassState.js"; */
  15. /** @import TilingScheme from "../Core/TilingScheme.js"; */
  16. /**
  17. * @typedef {object} TilesetJsonRoot
  18. * @property {object} [boundingVolume]
  19. * @property {number} [geometricError]
  20. * @property {"REPLACE"|"ADD"} [refine]
  21. * @property {unknown} [content]
  22. * @property {unknown[]} [children]
  23. * @ignore
  24. */
  25. const DEFAULT_MIN_ZOOM = 0;
  26. const DEFAULT_MAX_ZOOM = 14;
  27. const DEFAULT_REGION_MINIMUM_HEIGHT = -1000.0;
  28. const DEFAULT_REGION_MAXIMUM_HEIGHT = 10000.0;
  29. const EARTH_CIRCUMFERENCE_METERS =
  30. 2.0 * Math.PI * Ellipsoid.WGS84.maximumRadius;
  31. const WEB_MERCATOR_TILE_SIZE = 256.0;
  32. const scratchTileRectangle = new Rectangle();
  33. const scratchIntersectionRectangle = new Rectangle();
  34. /**
  35. * Base provider for URL-template vector sources that are rendered through a
  36. * runtime-generated 3D Tiles tileset.
  37. *
  38. * @private
  39. */
  40. class UrlTemplate3DTilesDataProvider {
  41. /**
  42. * @param {Resource|string} urlTemplate URL template containing {z}, {x}, and {y} placeholders.
  43. * @param {object} [options] Provider options.
  44. * @param {number} [options.minZoom=0] Minimum zoom level represented in the generated tileset.
  45. * @param {number} [options.maxZoom=14] Maximum zoom level represented in the generated tileset.
  46. * @param {Rectangle} [options.extent] Optional geographic extent in radians to constrain the generated tile tree.
  47. * @param {string} [options.featureIdProperty] Feature property name to use as feature ID when supported by content decoding.
  48. */
  49. constructor(urlTemplate, options) {
  50. options = options ?? {};
  51. this._resource = Resource.createIfNeeded(urlTemplate);
  52. // @ts-expect-error Missing types.
  53. this._urlTemplate = this._resource.url;
  54. this._extent = Rectangle.clone(options.extent);
  55. this._minZoom = options.minZoom ?? DEFAULT_MIN_ZOOM;
  56. this._maxZoom = options.maxZoom ?? DEFAULT_MAX_ZOOM;
  57. this._featureIdProperty = options.featureIdProperty;
  58. this._show = true;
  59. this._tileset = undefined;
  60. this._tilesetJsonUrl = undefined;
  61. }
  62. /**
  63. * Creates a provider from a URL template.
  64. *
  65. * @param {Resource|string} url URL template containing {z}, {x}, and {y} placeholders.
  66. * @param {object} [options] Provider options.
  67. * @returns {Promise<UrlTemplate3DTilesDataProvider>}
  68. */
  69. static async fromUrl(url, options) {
  70. const provider = new this(url, options);
  71. await provider._initializeTileset();
  72. return provider;
  73. }
  74. /**
  75. * URL template containing {z}/{x}/{y}.
  76. *
  77. * @type {string}
  78. * @readonly
  79. */
  80. get urlTemplate() {
  81. return this._urlTemplate;
  82. }
  83. /**
  84. * Resource derived from the URL template.
  85. *
  86. * @type {Resource}
  87. * @readonly
  88. */
  89. get resource() {
  90. return this._resource;
  91. }
  92. /**
  93. * Optional geographic extent in radians used to generate tile headers.
  94. *
  95. * @type {Rectangle|undefined}
  96. * @readonly
  97. */
  98. get extent() {
  99. return this._extent;
  100. }
  101. /**
  102. * Backing 3D Tileset.
  103. *
  104. * @type {Cesium3DTileset|undefined}
  105. * @readonly
  106. */
  107. get tileset() {
  108. return this._tileset;
  109. }
  110. /**
  111. * Determines if the generated tileset is shown.
  112. *
  113. * @type {boolean}
  114. */
  115. get show() {
  116. return this._show;
  117. }
  118. set show(value) {
  119. this._show = value;
  120. if (defined(this._tileset)) {
  121. this._tileset.show = value;
  122. }
  123. }
  124. /** @protected */
  125. _createRuntimeTilesetOptions() {
  126. return {
  127. minZoom: this._minZoom,
  128. maxZoom: this._maxZoom,
  129. extent: this._extent,
  130. };
  131. }
  132. /**
  133. * @protected
  134. * @returns {object}
  135. */
  136. _createTilesetLoadOptions() {
  137. return {};
  138. }
  139. /**
  140. * @protected
  141. * @param {Cesium3DTileset} _tileset
  142. */
  143. _configureTileset(_tileset) {}
  144. /**
  145. * Subclasses must return a runtime content codec describing how to turn
  146. * a downloaded tile payload into a {@link Cesium3DTileContent}. See
  147. * {@link Cesium3DTileset#_runtimeContentCodec} for the expected shape.
  148. *
  149. * @protected
  150. * @returns {object}
  151. */
  152. _createCodec() {
  153. //>>includeStart('debug', pragmas.debug);
  154. throw new DeveloperError(
  155. "UrlTemplate3DTilesDataProvider subclasses must implement _createCodec().",
  156. );
  157. //>>includeEnd('debug');
  158. }
  159. /**
  160. * @private
  161. */
  162. async _initializeTileset() {
  163. const tilesetJson = buildRuntimeTilesetJson(
  164. this._resource,
  165. this._createRuntimeTilesetOptions(),
  166. );
  167. const tilesetBlob = new Blob([JSON.stringify(tilesetJson)], {
  168. type: "application/json",
  169. });
  170. const tilesetUrl = URL.createObjectURL(tilesetBlob);
  171. this._tilesetJsonUrl = tilesetUrl;
  172. try {
  173. this._tileset = await Cesium3DTileset.fromUrl(
  174. tilesetUrl,
  175. this._createTilesetLoadOptions(),
  176. );
  177. } catch (error) {
  178. URL.revokeObjectURL(tilesetUrl);
  179. this._tilesetJsonUrl = undefined;
  180. throw error;
  181. }
  182. URL.revokeObjectURL(tilesetUrl);
  183. this._tilesetJsonUrl = undefined;
  184. this._configureTileset(this._tileset);
  185. this._tileset._runtimeContentCodec = this._createCodec();
  186. this._tileset.show = this._show;
  187. }
  188. /**
  189. * @private
  190. * @param {FrameState} frameState
  191. */
  192. update(frameState) {
  193. if (defined(this._tileset)) {
  194. this._tileset.update(frameState);
  195. }
  196. }
  197. /**
  198. * @private
  199. * @param {FrameState} frameState
  200. */
  201. prePassesUpdate(frameState) {
  202. if (defined(this._tileset)) {
  203. this._tileset.prePassesUpdate(frameState);
  204. }
  205. }
  206. /**
  207. * @private
  208. * @param {FrameState} frameState
  209. */
  210. postPassesUpdate(frameState) {
  211. if (defined(this._tileset)) {
  212. this._tileset.postPassesUpdate(frameState);
  213. }
  214. }
  215. /**
  216. * @private
  217. * @param {FrameState} frameState
  218. * @param {PassState} passState
  219. */
  220. updateForPass(frameState, passState) {
  221. if (defined(this._tileset)) {
  222. this._tileset.updateForPass(frameState, passState);
  223. }
  224. }
  225. isDestroyed() {
  226. return false;
  227. }
  228. destroy() {
  229. if (defined(this._tileset)) {
  230. this._tileset.destroy();
  231. this._tileset = undefined;
  232. }
  233. if (defined(this._tilesetJsonUrl)) {
  234. URL.revokeObjectURL(this._tilesetJsonUrl);
  235. this._tilesetJsonUrl = undefined;
  236. }
  237. this._resource = undefined;
  238. this._extent = undefined;
  239. this._featureIdProperty = undefined;
  240. return destroyObject(this);
  241. }
  242. }
  243. /**
  244. * @param {Resource} resource
  245. * @param {object} options
  246. * @param {number} options.minZoom
  247. * @param {number} options.maxZoom
  248. * @param {Rectangle} [options.extent]
  249. * @returns {object}
  250. * @ignore
  251. */
  252. function buildRuntimeTilesetJson(resource, options) {
  253. const tilingScheme = new WebMercatorTilingScheme();
  254. const extent = defined(options.extent)
  255. ? Rectangle.clone(options.extent)
  256. : Rectangle.clone(tilingScheme.rectangle);
  257. const minLevelRange = computeTileRangeForExtent(
  258. tilingScheme,
  259. extent,
  260. options.minZoom,
  261. );
  262. /** @type {TilesetJsonRoot} */
  263. const root = {
  264. boundingVolume: {
  265. region: rectangleToRegion(extent),
  266. },
  267. // Root has no renderable content, so keep a coarse error to ensure
  268. // refinement reaches the first renderable zoom even when minZoom is high.
  269. geometricError: computeGeometricError(0),
  270. refine: "REPLACE",
  271. children: [],
  272. };
  273. for (let y = minLevelRange.minY; y <= minLevelRange.maxY; y++) {
  274. for (let x = minLevelRange.minX; x <= minLevelRange.maxX; x++) {
  275. const child = buildTileNode(
  276. tilingScheme,
  277. resource,
  278. extent,
  279. options.minZoom,
  280. options.maxZoom,
  281. x,
  282. y,
  283. );
  284. if (defined(child)) {
  285. root.children.push(child);
  286. }
  287. }
  288. }
  289. if (root.children.length === 0) {
  290. root.geometricError = 0.0;
  291. }
  292. return {
  293. asset: {
  294. version: "1.1",
  295. },
  296. geometricError: root.geometricError,
  297. root: root,
  298. };
  299. }
  300. /**
  301. * @param {TilingScheme} tilingScheme
  302. * @param {Resource} resource
  303. * @param {Rectangle} extent
  304. * @param {number} level
  305. * @param {number} maxZoom
  306. * @param {number} x
  307. * @param {number} y
  308. * @ignore
  309. */
  310. function buildTileNode(tilingScheme, resource, extent, level, maxZoom, x, y) {
  311. if (
  312. !tileIntersectsExtent(
  313. tilingScheme,
  314. level,
  315. x,
  316. y,
  317. extent,
  318. scratchTileRectangle,
  319. scratchIntersectionRectangle,
  320. )
  321. ) {
  322. return undefined;
  323. }
  324. const tileRectangle = tilingScheme.tileXYToRectangle(
  325. x,
  326. y,
  327. level,
  328. new Rectangle(),
  329. );
  330. /** @type {TilesetJsonRoot} */
  331. const node = {
  332. boundingVolume: {
  333. region: rectangleToRegion(tileRectangle),
  334. },
  335. geometricError: level < maxZoom ? computeGeometricError(level) : 0.0,
  336. refine: "REPLACE",
  337. content: {
  338. uri: resolveTileUrl(resource, level, x, y),
  339. },
  340. };
  341. if (level >= maxZoom) {
  342. return node;
  343. }
  344. const childLevel = level + 1;
  345. const children = [];
  346. for (let childY = y * 2; childY <= y * 2 + 1; childY++) {
  347. for (let childX = x * 2; childX <= x * 2 + 1; childX++) {
  348. const child = buildTileNode(
  349. tilingScheme,
  350. resource,
  351. extent,
  352. childLevel,
  353. maxZoom,
  354. childX,
  355. childY,
  356. );
  357. if (defined(child)) {
  358. children.push(child);
  359. }
  360. }
  361. }
  362. if (children.length > 0) {
  363. node.children = children;
  364. } else {
  365. node.geometricError = 0.0;
  366. }
  367. return node;
  368. }
  369. /**
  370. * @param {Resource} resource
  371. * @param {number} level
  372. * @param {number} x
  373. * @param {number} y
  374. * @ignore
  375. */
  376. function resolveTileUrl(resource, level, x, y) {
  377. // @ts-expect-error Missing types.
  378. const template = resource.url;
  379. const tileUrl = template
  380. .replace(/\{z\}/gi, `${level}`)
  381. .replace(/\{x\}/gi, `${x}`)
  382. .replace(/\{y\}/gi, `${y}`);
  383. return getAbsoluteUri(tileUrl);
  384. }
  385. /**
  386. * @param {number} level
  387. * @ignore
  388. */
  389. function computeGeometricError(level) {
  390. return EARTH_CIRCUMFERENCE_METERS / ((1 << level) * WEB_MERCATOR_TILE_SIZE);
  391. }
  392. /**
  393. * @param {Rectangle} rectangle
  394. * @returns {number[]}
  395. * @ignore
  396. */
  397. function rectangleToRegion(rectangle) {
  398. return [
  399. rectangle.west,
  400. rectangle.south,
  401. rectangle.east,
  402. rectangle.north,
  403. DEFAULT_REGION_MINIMUM_HEIGHT,
  404. DEFAULT_REGION_MAXIMUM_HEIGHT,
  405. ];
  406. }
  407. /**
  408. * @param {TilingScheme} tilingScheme
  409. * @param {Rectangle} extent
  410. * @param {number} level
  411. * @ignore
  412. */
  413. function computeTileRangeForExtent(tilingScheme, extent, level) {
  414. const maxIndex = (1 << level) - 1;
  415. const nw = Cartographic.fromRadians(extent.west, extent.north);
  416. const se = Cartographic.fromRadians(extent.east, extent.south);
  417. const nwTile = tilingScheme.positionToTileXY(nw, level);
  418. const seTile = tilingScheme.positionToTileXY(se, level);
  419. if (!defined(nwTile) || !defined(seTile) || extent.west > extent.east) {
  420. return {
  421. minX: 0,
  422. maxX: maxIndex,
  423. minY: 0,
  424. maxY: maxIndex,
  425. };
  426. }
  427. return {
  428. minX: CesiumMath.clamp(Math.min(nwTile.x, seTile.x), 0, maxIndex),
  429. maxX: CesiumMath.clamp(Math.max(nwTile.x, seTile.x), 0, maxIndex),
  430. minY: CesiumMath.clamp(Math.min(nwTile.y, seTile.y), 0, maxIndex),
  431. maxY: CesiumMath.clamp(Math.max(nwTile.y, seTile.y), 0, maxIndex),
  432. };
  433. }
  434. /**
  435. * @param {TilingScheme} tilingScheme
  436. * @param {number} level
  437. * @param {number} x
  438. * @param {number} y
  439. * @param {Rectangle} extent
  440. * @param {Rectangle} tileRectangleScratch
  441. * @param {Rectangle} intersectionScratch
  442. * @ignore
  443. */
  444. function tileIntersectsExtent(
  445. tilingScheme,
  446. level,
  447. x,
  448. y,
  449. extent,
  450. tileRectangleScratch,
  451. intersectionScratch,
  452. ) {
  453. const tileRectangle = tilingScheme.tileXYToRectangle(
  454. x,
  455. y,
  456. level,
  457. tileRectangleScratch,
  458. );
  459. return defined(
  460. Rectangle.intersection(tileRectangle, extent, intersectionScratch),
  461. );
  462. }
  463. export default UrlTemplate3DTilesDataProvider;