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

TileMapServiceImageryProvider.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. import Cartesian2 from "../Core/Cartesian2.js";
  2. import Cartographic from "../Core/Cartographic.js";
  3. import Check from "../Core/Check.js";
  4. import Frozen from "../Core/Frozen.js";
  5. import defined from "../Core/defined.js";
  6. import GeographicProjection from "../Core/GeographicProjection.js";
  7. import GeographicTilingScheme from "../Core/GeographicTilingScheme.js";
  8. import Rectangle from "../Core/Rectangle.js";
  9. import RequestErrorEvent from "../Core/RequestErrorEvent.js";
  10. import Resource from "../Core/Resource.js";
  11. import RuntimeError from "../Core/RuntimeError.js";
  12. import TileProviderError from "../Core/TileProviderError.js";
  13. import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
  14. import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
  15. /**
  16. * @typedef {object} TileMapServiceImageryProvider.ConstructorOptions
  17. *
  18. * Initialization options for the TileMapServiceImageryProvider constructor
  19. *
  20. * @property {string} [fileExtension='png'] The file extension for images on the server.
  21. * @property {Credit|string} [credit=''] A credit for the data source, which is displayed on the canvas.
  22. * @property {number} [minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
  23. * this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
  24. * to result in rendering problems.
  25. * @property {number} [maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
  26. * @property {Rectangle} [rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
  27. * @property {TilingScheme} [tilingScheme] The tiling scheme specifying how the ellipsoidal
  28. * surface is broken into tiles. If this parameter is not provided, a {@link WebMercatorTilingScheme}
  29. * is used.
  30. * @property {Ellipsoid} [ellipsoid] The ellipsoid. If the tilingScheme is specified,
  31. * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
  32. * parameter is specified, the WGS84 ellipsoid is used.
  33. * @property {number} [tileWidth=256] Pixel width of image tiles.
  34. * @property {number} [tileHeight=256] Pixel height of image tiles.
  35. * @property {boolean} [flipXY] Older versions of gdal2tiles.py flipped X and Y values in tilemapresource.xml.
  36. * @property {TileDiscardPolicy} [tileDiscardPolicy] A policy for discarding tile images according to some criteria
  37. * Specifying this option will do the same, allowing for loading of these incorrect tilesets.
  38. */
  39. /**
  40. * <div class="notice">
  41. * To construct a TileMapServiceImageryProvider, call {@link TileMapServiceImageryProvider.fromUrl}. Do not call the constructor directly.
  42. * </div>
  43. *
  44. * An imagery provider that provides tiled imagery as generated by
  45. * {@link http://www.maptiler.org/|MapTiler}, {@link http://www.klokan.cz/projects/gdal2tiles/|GDAL2Tiles}, etc.
  46. *
  47. * @alias TileMapServiceImageryProvider
  48. * @constructor
  49. * @extends UrlTemplateImageryProvider
  50. *
  51. * @param {TileMapServiceImageryProvider.ConstructorOptions} [options] Object describing initialization options
  52. *
  53. * @see ArcGisMapServerImageryProvider
  54. * @see BingMapsImageryProvider
  55. * @see GoogleEarthEnterpriseMapsProvider
  56. * @see OpenStreetMapImageryProvider
  57. * @see SingleTileImageryProvider
  58. * @see WebMapServiceImageryProvider
  59. * @see WebMapTileServiceImageryProvider
  60. * @see UrlTemplateImageryProvider
  61. *
  62. * @example
  63. * const tms = await Cesium.TileMapServiceImageryProvider.fromUrl(
  64. * "../images/cesium_maptiler/Cesium_Logo_Color", {
  65. * fileExtension: 'png',
  66. * maximumLevel: 4,
  67. * rectangle: new Cesium.Rectangle(
  68. * Cesium.Math.toRadians(-120.0),
  69. * Cesium.Math.toRadians(20.0),
  70. * Cesium.Math.toRadians(-60.0),
  71. * Cesium.Math.toRadians(40.0))
  72. * });
  73. */
  74. function TileMapServiceImageryProvider(options) {
  75. UrlTemplateImageryProvider.call(this, options);
  76. }
  77. TileMapServiceImageryProvider._requestMetadata = async function (
  78. options,
  79. tmsResource,
  80. xmlResource,
  81. provider,
  82. ) {
  83. // Try to load remaining parameters from XML
  84. try {
  85. const xml = await xmlResource.fetchXML();
  86. return TileMapServiceImageryProvider._metadataSuccess(
  87. xml,
  88. options,
  89. tmsResource,
  90. xmlResource,
  91. provider,
  92. );
  93. } catch (e) {
  94. if (e instanceof RequestErrorEvent) {
  95. return TileMapServiceImageryProvider._metadataFailure(
  96. options,
  97. tmsResource,
  98. );
  99. }
  100. throw e;
  101. }
  102. };
  103. /**
  104. * Creates a TileMapServiceImageryProvider from the specified url.
  105. *
  106. * @param {Resource|string} url Path to image tiles on server.
  107. * @param {TileMapServiceImageryProvider.ConstructorOptions} [options] Object describing initialization options.
  108. * @returns {Promise<TileMapServiceImageryProvider>} A promise that resolves to the created TileMapServiceImageryProvider.
  109. *
  110. * @example
  111. * const tms = await Cesium.TileMapServiceImageryProvider.fromUrl(
  112. * '../images/cesium_maptiler/Cesium_Logo_Color', {
  113. * fileExtension: 'png',
  114. * maximumLevel: 4,
  115. * rectangle: new Cesium.Rectangle(
  116. * Cesium.Math.toRadians(-120.0),
  117. * Cesium.Math.toRadians(20.0),
  118. * Cesium.Math.toRadians(-60.0),
  119. * Cesium.Math.toRadians(40.0))
  120. * });
  121. *
  122. * @exception {RuntimeError} Unable to find expected tilesets or bbox attributes in tilemapresource.xml
  123. * @exception {RuntimeError} tilemapresource.xml specifies an unsupported profile attribute
  124. */
  125. TileMapServiceImageryProvider.fromUrl = async function (url, options) {
  126. //>>includeStart('debug', pragmas.debug);
  127. Check.defined("url", url);
  128. //>>includeEnd('debug');
  129. const resource = Resource.createIfNeeded(url);
  130. resource.appendForwardSlash();
  131. const tmsResource = resource;
  132. const xmlResource = resource.getDerivedResource({
  133. url: "tilemapresource.xml",
  134. });
  135. options = options ?? Frozen.EMPTY_OBJECT;
  136. const metadata = await TileMapServiceImageryProvider._requestMetadata(
  137. options,
  138. tmsResource,
  139. xmlResource,
  140. );
  141. return new TileMapServiceImageryProvider(metadata);
  142. };
  143. if (defined(Object.create)) {
  144. TileMapServiceImageryProvider.prototype = Object.create(
  145. UrlTemplateImageryProvider.prototype,
  146. );
  147. TileMapServiceImageryProvider.prototype.constructor =
  148. TileMapServiceImageryProvider;
  149. }
  150. /**
  151. * Mutates the properties of a given rectangle so it does not extend outside of the given tiling scheme's rectangle
  152. * @private
  153. */
  154. function confineRectangleToTilingScheme(rectangle, tilingScheme) {
  155. if (rectangle.west < tilingScheme.rectangle.west) {
  156. rectangle.west = tilingScheme.rectangle.west;
  157. }
  158. if (rectangle.east > tilingScheme.rectangle.east) {
  159. rectangle.east = tilingScheme.rectangle.east;
  160. }
  161. if (rectangle.south < tilingScheme.rectangle.south) {
  162. rectangle.south = tilingScheme.rectangle.south;
  163. }
  164. if (rectangle.north > tilingScheme.rectangle.north) {
  165. rectangle.north = tilingScheme.rectangle.north;
  166. }
  167. return rectangle;
  168. }
  169. function calculateSafeMinimumDetailLevel(
  170. tilingScheme,
  171. rectangle,
  172. minimumLevel,
  173. ) {
  174. // Check the number of tiles at the minimum level. If it's more than four,
  175. // try requesting the lower levels anyway, because starting at the higher minimum
  176. // level will cause too many tiles to be downloaded and rendered.
  177. const swTile = tilingScheme.positionToTileXY(
  178. Rectangle.southwest(rectangle),
  179. minimumLevel,
  180. );
  181. const neTile = tilingScheme.positionToTileXY(
  182. Rectangle.northeast(rectangle),
  183. minimumLevel,
  184. );
  185. const tileCount =
  186. (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
  187. if (tileCount > 4) {
  188. return 0;
  189. }
  190. return minimumLevel;
  191. }
  192. /**
  193. * Parses the results of a successful xml request
  194. * @private
  195. *
  196. * @param {object} xml
  197. * @param {TileMapServiceImageryProvider.ConstructorOptions} options
  198. * @param {Resource} tmsResource
  199. * @param {Resource} xmlResource
  200. * @returns {UrlTemplateImageryProvider.ConstructorOptions}
  201. */
  202. TileMapServiceImageryProvider._metadataSuccess = function (
  203. xml,
  204. options,
  205. tmsResource,
  206. xmlResource,
  207. provider,
  208. ) {
  209. const tileFormatRegex = /tileformat/i;
  210. const tileSetRegex = /tileset/i;
  211. const tileSetsRegex = /tilesets/i;
  212. const bboxRegex = /boundingbox/i;
  213. let format, bbox, tilesets;
  214. const tilesetsList = []; //list of TileSets
  215. // Allowing options properties (already copied to that) to override XML values
  216. // Iterate XML Document nodes for properties
  217. const nodeList = xml.childNodes[0].childNodes;
  218. for (let i = 0; i < nodeList.length; i++) {
  219. if (tileFormatRegex.test(nodeList.item(i).nodeName)) {
  220. format = nodeList.item(i);
  221. } else if (tileSetsRegex.test(nodeList.item(i).nodeName)) {
  222. tilesets = nodeList.item(i); // Node list of TileSets
  223. const tileSetNodes = nodeList.item(i).childNodes;
  224. // Iterate the nodes to find all TileSets
  225. for (let j = 0; j < tileSetNodes.length; j++) {
  226. if (tileSetRegex.test(tileSetNodes.item(j).nodeName)) {
  227. // Add them to tilesets list
  228. tilesetsList.push(tileSetNodes.item(j));
  229. }
  230. }
  231. } else if (bboxRegex.test(nodeList.item(i).nodeName)) {
  232. bbox = nodeList.item(i);
  233. }
  234. }
  235. let message;
  236. if (!defined(tilesets) || !defined(bbox)) {
  237. message = `Unable to find expected tilesets or bbox attributes in ${xmlResource.url}.`;
  238. if (defined(provider)) {
  239. TileProviderError.reportError(
  240. undefined,
  241. provider,
  242. provider.errorEvent,
  243. message,
  244. );
  245. }
  246. throw new RuntimeError(message);
  247. }
  248. const fileExtension =
  249. options.fileExtension ?? format.getAttribute("extension");
  250. const tileWidth =
  251. options.tileWidth ?? parseInt(format.getAttribute("width"), 10);
  252. const tileHeight =
  253. options.tileHeight ?? parseInt(format.getAttribute("height"), 10);
  254. let minimumLevel =
  255. options.minimumLevel ?? parseInt(tilesetsList[0].getAttribute("order"), 10);
  256. const maximumLevel =
  257. options.maximumLevel ??
  258. parseInt(tilesetsList[tilesetsList.length - 1].getAttribute("order"), 10);
  259. const tilingSchemeName = tilesets.getAttribute("profile");
  260. let tilingScheme = options.tilingScheme;
  261. if (!defined(tilingScheme)) {
  262. if (
  263. tilingSchemeName === "geodetic" ||
  264. tilingSchemeName === "global-geodetic"
  265. ) {
  266. tilingScheme = new GeographicTilingScheme({
  267. ellipsoid: options.ellipsoid,
  268. });
  269. } else if (
  270. tilingSchemeName === "mercator" ||
  271. tilingSchemeName === "global-mercator"
  272. ) {
  273. tilingScheme = new WebMercatorTilingScheme({
  274. ellipsoid: options.ellipsoid,
  275. });
  276. } else {
  277. message = `${xmlResource.url} specifies an unsupported profile attribute, ${tilingSchemeName}.`;
  278. if (defined(provider)) {
  279. TileProviderError.reportError(
  280. undefined,
  281. provider,
  282. provider.errorEvent,
  283. message,
  284. );
  285. }
  286. throw new RuntimeError(message);
  287. }
  288. }
  289. // rectangle handling
  290. let rectangle = Rectangle.clone(options.rectangle);
  291. if (!defined(rectangle)) {
  292. let sw;
  293. let ne;
  294. let swXY;
  295. let neXY;
  296. // In older versions of gdal x and y values were flipped, which is why we check for an option to flip
  297. // the values here as well. Unfortunately there is no way to autodetect whether flipping is needed.
  298. const flipXY = options.flipXY ?? false;
  299. if (flipXY) {
  300. swXY = new Cartesian2(
  301. parseFloat(bbox.getAttribute("miny")),
  302. parseFloat(bbox.getAttribute("minx")),
  303. );
  304. neXY = new Cartesian2(
  305. parseFloat(bbox.getAttribute("maxy")),
  306. parseFloat(bbox.getAttribute("maxx")),
  307. );
  308. } else {
  309. swXY = new Cartesian2(
  310. parseFloat(bbox.getAttribute("minx")),
  311. parseFloat(bbox.getAttribute("miny")),
  312. );
  313. neXY = new Cartesian2(
  314. parseFloat(bbox.getAttribute("maxx")),
  315. parseFloat(bbox.getAttribute("maxy")),
  316. );
  317. }
  318. // Determine based on the profile attribute if this tileset was generated by gdal2tiles.py, which
  319. // uses 'mercator' and 'geodetic' profiles, or by a tool compliant with the TMS standard, which is
  320. // 'global-mercator' and 'global-geodetic' profiles. In the gdal2Tiles case, X and Y are always in
  321. // geodetic degrees.
  322. const isGdal2tiles =
  323. tilingSchemeName === "geodetic" || tilingSchemeName === "mercator";
  324. if (
  325. tilingScheme.projection instanceof GeographicProjection ||
  326. isGdal2tiles
  327. ) {
  328. sw = Cartographic.fromDegrees(swXY.x, swXY.y);
  329. ne = Cartographic.fromDegrees(neXY.x, neXY.y);
  330. } else {
  331. const projection = tilingScheme.projection;
  332. sw = projection.unproject(swXY);
  333. ne = projection.unproject(neXY);
  334. }
  335. rectangle = new Rectangle(
  336. sw.longitude,
  337. sw.latitude,
  338. ne.longitude,
  339. ne.latitude,
  340. );
  341. }
  342. // The rectangle must not be outside the bounds allowed by the tiling scheme.
  343. rectangle = confineRectangleToTilingScheme(rectangle, tilingScheme);
  344. // clamp our minimum detail level to something that isn't going to request a ridiculous number of tiles
  345. minimumLevel = calculateSafeMinimumDetailLevel(
  346. tilingScheme,
  347. rectangle,
  348. minimumLevel,
  349. );
  350. const templateResource = tmsResource.getDerivedResource({
  351. url: `{z}/{x}/{reverseY}.${fileExtension}`,
  352. });
  353. return {
  354. url: templateResource,
  355. tilingScheme: tilingScheme,
  356. rectangle: rectangle,
  357. tileWidth: tileWidth,
  358. tileHeight: tileHeight,
  359. minimumLevel: minimumLevel,
  360. maximumLevel: maximumLevel,
  361. tileDiscardPolicy: options.tileDiscardPolicy,
  362. credit: options.credit,
  363. };
  364. };
  365. /**
  366. * Handle xml request failure by providing the default values
  367. * @private
  368. *
  369. * @param {TileMapServiceImageryProvider.ConstructorOptions} options
  370. * @param {Resource} tmsResource
  371. * @returns {UrlTemplateImageryProvider.ConstructorOptions}
  372. */
  373. TileMapServiceImageryProvider._metadataFailure = function (
  374. options,
  375. tmsResource,
  376. ) {
  377. // Can't load XML, still allow options and defaults
  378. const fileExtension = options.fileExtension ?? "png";
  379. const tileWidth = options.tileWidth ?? 256;
  380. const tileHeight = options.tileHeight ?? 256;
  381. const maximumLevel = options.maximumLevel;
  382. const tilingScheme = defined(options.tilingScheme)
  383. ? options.tilingScheme
  384. : new WebMercatorTilingScheme({ ellipsoid: options.ellipsoid });
  385. let rectangle = options.rectangle ?? tilingScheme.rectangle;
  386. // The rectangle must not be outside the bounds allowed by the tiling scheme.
  387. rectangle = confineRectangleToTilingScheme(rectangle, tilingScheme);
  388. // make sure we use a safe minimum detail level, so we don't request a ridiculous number of tiles
  389. const minimumLevel = calculateSafeMinimumDetailLevel(
  390. tilingScheme,
  391. rectangle,
  392. options.minimumLevel,
  393. );
  394. const templateResource = tmsResource.getDerivedResource({
  395. url: `{z}/{x}/{reverseY}.${fileExtension}`,
  396. });
  397. return {
  398. url: templateResource,
  399. tilingScheme: tilingScheme,
  400. rectangle: rectangle,
  401. tileWidth: tileWidth,
  402. tileHeight: tileHeight,
  403. minimumLevel: minimumLevel,
  404. maximumLevel: maximumLevel,
  405. tileDiscardPolicy: options.tileDiscardPolicy,
  406. credit: options.credit,
  407. };
  408. };
  409. export default TileMapServiceImageryProvider;