123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408 |
- import Cartesian2 from "../Core/Cartesian2.js";
- import Cartographic from "../Core/Cartographic.js";
- import defaultValue from "../Core/defaultValue.js";
- import defer from "../Core/defer.js";
- import defined from "../Core/defined.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import GeographicProjection from "../Core/GeographicProjection.js";
- import GeographicTilingScheme from "../Core/GeographicTilingScheme.js";
- import Rectangle from "../Core/Rectangle.js";
- import Resource from "../Core/Resource.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import TileProviderError from "../Core/TileProviderError.js";
- import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
- import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
- /**
- * @typedef {Object} TileMapServiceImageryProvider.ConstructorOptions
- *
- * Initialization options for the TileMapServiceImageryProvider constructor
- *
- * @property {Resource|String|Promise<Resource>|Promise<String>} [url='.'] Path to image tiles on server.
- * @property {String} [fileExtension='png'] The file extension for images on the server.
- * @property {Credit|String} [credit=''] A credit for the data source, which is displayed on the canvas.
- * @property {Number} [minimumLevel=0] The minimum level-of-detail supported by the imagery provider. Take care when specifying
- * this that the number of tiles at the minimum level is small, such as four or less. A larger number is likely
- * to result in rendering problems.
- * @property {Number} [maximumLevel] The maximum level-of-detail supported by the imagery provider, or undefined if there is no limit.
- * @property {Rectangle} [rectangle=Rectangle.MAX_VALUE] The rectangle, in radians, covered by the image.
- * @property {TilingScheme} [tilingScheme] The tiling scheme specifying how the ellipsoidal
- * surface is broken into tiles. If this parameter is not provided, a {@link WebMercatorTilingScheme}
- * is used.
- * @property {Ellipsoid} [ellipsoid] The ellipsoid. If the tilingScheme is specified,
- * this parameter is ignored and the tiling scheme's ellipsoid is used instead. If neither
- * parameter is specified, the WGS84 ellipsoid is used.
- * @property {Number} [tileWidth=256] Pixel width of image tiles.
- * @property {Number} [tileHeight=256] Pixel height of image tiles.
- * @property {Boolean} [flipXY] Older versions of gdal2tiles.py flipped X and Y values in tilemapresource.xml.
- * Specifying this option will do the same, allowing for loading of these incorrect tilesets.
- */
- /**
- * An imagery provider that provides tiled imagery as generated by
- * {@link http://www.maptiler.org/|MapTiler}, {@link http://www.klokan.cz/projects/gdal2tiles/|GDAL2Tiles}, etc.
- *
- * @alias TileMapServiceImageryProvider
- * @constructor
- * @extends UrlTemplateImageryProvider
- *
- * @param {TileMapServiceImageryProvider.ConstructorOptions} options Object describing initialization options
- *
- * @see ArcGisMapServerImageryProvider
- * @see BingMapsImageryProvider
- * @see GoogleEarthEnterpriseMapsProvider
- * @see OpenStreetMapImageryProvider
- * @see SingleTileImageryProvider
- * @see WebMapServiceImageryProvider
- * @see WebMapTileServiceImageryProvider
- * @see UrlTemplateImageryProvider
- *
- * @example
- * const tms = new Cesium.TileMapServiceImageryProvider({
- * url : '../images/cesium_maptiler/Cesium_Logo_Color',
- * fileExtension: 'png',
- * maximumLevel: 4,
- * rectangle: new Cesium.Rectangle(
- * Cesium.Math.toRadians(-120.0),
- * Cesium.Math.toRadians(20.0),
- * Cesium.Math.toRadians(-60.0),
- * Cesium.Math.toRadians(40.0))
- * });
- */
- function TileMapServiceImageryProvider(options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- //>>includeStart('debug', pragmas.debug);
- if (!defined(options.url)) {
- throw new DeveloperError("options.url is required.");
- }
- //>>includeEnd('debug');
- const deferred = defer();
- UrlTemplateImageryProvider.call(this, deferred.promise);
- this._tmsResource = undefined;
- this._xmlResource = undefined;
- this._options = options;
- this._deferred = deferred;
- this._metadataError = undefined;
- this._metadataSuccess = this._metadataSuccess.bind(this);
- this._metadataFailure = this._metadataFailure.bind(this);
- this._requestMetadata = this._requestMetadata.bind(this);
- let resource;
- const that = this;
- Promise.resolve(options.url)
- .then(function (url) {
- resource = Resource.createIfNeeded(url);
- resource.appendForwardSlash();
- that._tmsResource = resource;
- that._xmlResource = resource.getDerivedResource({
- url: "tilemapresource.xml",
- });
- that._requestMetadata();
- })
- .catch(function (e) {
- deferred.reject(e);
- });
- }
- if (defined(Object.create)) {
- TileMapServiceImageryProvider.prototype = Object.create(
- UrlTemplateImageryProvider.prototype
- );
- TileMapServiceImageryProvider.prototype.constructor = TileMapServiceImageryProvider;
- }
- TileMapServiceImageryProvider.prototype._requestMetadata = function () {
- // Try to load remaining parameters from XML
- this._xmlResource
- .fetchXML()
- .then(this._metadataSuccess)
- .catch(this._metadataFailure);
- };
- /**
- * Mutates the properties of a given rectangle so it does not extend outside of the given tiling scheme's rectangle
- * @private
- */
- function confineRectangleToTilingScheme(rectangle, tilingScheme) {
- if (rectangle.west < tilingScheme.rectangle.west) {
- rectangle.west = tilingScheme.rectangle.west;
- }
- if (rectangle.east > tilingScheme.rectangle.east) {
- rectangle.east = tilingScheme.rectangle.east;
- }
- if (rectangle.south < tilingScheme.rectangle.south) {
- rectangle.south = tilingScheme.rectangle.south;
- }
- if (rectangle.north > tilingScheme.rectangle.north) {
- rectangle.north = tilingScheme.rectangle.north;
- }
- return rectangle;
- }
- function calculateSafeMinimumDetailLevel(
- tilingScheme,
- rectangle,
- minimumLevel
- ) {
- // Check the number of tiles at the minimum level. If it's more than four,
- // try requesting the lower levels anyway, because starting at the higher minimum
- // level will cause too many tiles to be downloaded and rendered.
- const swTile = tilingScheme.positionToTileXY(
- Rectangle.southwest(rectangle),
- minimumLevel
- );
- const neTile = tilingScheme.positionToTileXY(
- Rectangle.northeast(rectangle),
- minimumLevel
- );
- const tileCount =
- (Math.abs(neTile.x - swTile.x) + 1) * (Math.abs(neTile.y - swTile.y) + 1);
- if (tileCount > 4) {
- return 0;
- }
- return minimumLevel;
- }
- TileMapServiceImageryProvider.prototype._metadataSuccess = function (xml) {
- const tileFormatRegex = /tileformat/i;
- const tileSetRegex = /tileset/i;
- const tileSetsRegex = /tilesets/i;
- const bboxRegex = /boundingbox/i;
- let format, bbox, tilesets;
- const tilesetsList = []; //list of TileSets
- const xmlResource = this._xmlResource;
- let metadataError = this._metadataError;
- const deferred = this._deferred;
- const requestMetadata = this._requestMetadata;
- // Allowing options properties (already copied to that) to override XML values
- // Iterate XML Document nodes for properties
- const nodeList = xml.childNodes[0].childNodes;
- for (let i = 0; i < nodeList.length; i++) {
- if (tileFormatRegex.test(nodeList.item(i).nodeName)) {
- format = nodeList.item(i);
- } else if (tileSetsRegex.test(nodeList.item(i).nodeName)) {
- tilesets = nodeList.item(i); // Node list of TileSets
- const tileSetNodes = nodeList.item(i).childNodes;
- // Iterate the nodes to find all TileSets
- for (let j = 0; j < tileSetNodes.length; j++) {
- if (tileSetRegex.test(tileSetNodes.item(j).nodeName)) {
- // Add them to tilesets list
- tilesetsList.push(tileSetNodes.item(j));
- }
- }
- } else if (bboxRegex.test(nodeList.item(i).nodeName)) {
- bbox = nodeList.item(i);
- }
- }
- let message;
- if (!defined(tilesets) || !defined(bbox)) {
- message = `Unable to find expected tilesets or bbox attributes in ${xmlResource.url}.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- this,
- this.errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestMetadata
- );
- if (!metadataError.retry) {
- deferred.reject(new RuntimeError(message));
- }
- this._metadataError = metadataError;
- return;
- }
- const options = this._options;
- const fileExtension = defaultValue(
- options.fileExtension,
- format.getAttribute("extension")
- );
- const tileWidth = defaultValue(
- options.tileWidth,
- parseInt(format.getAttribute("width"), 10)
- );
- const tileHeight = defaultValue(
- options.tileHeight,
- parseInt(format.getAttribute("height"), 10)
- );
- let minimumLevel = defaultValue(
- options.minimumLevel,
- parseInt(tilesetsList[0].getAttribute("order"), 10)
- );
- const maximumLevel = defaultValue(
- options.maximumLevel,
- parseInt(tilesetsList[tilesetsList.length - 1].getAttribute("order"), 10)
- );
- const tilingSchemeName = tilesets.getAttribute("profile");
- let tilingScheme = options.tilingScheme;
- if (!defined(tilingScheme)) {
- if (
- tilingSchemeName === "geodetic" ||
- tilingSchemeName === "global-geodetic"
- ) {
- tilingScheme = new GeographicTilingScheme({
- ellipsoid: options.ellipsoid,
- });
- } else if (
- tilingSchemeName === "mercator" ||
- tilingSchemeName === "global-mercator"
- ) {
- tilingScheme = new WebMercatorTilingScheme({
- ellipsoid: options.ellipsoid,
- });
- } else {
- message = `${xmlResource.url}specifies an unsupported profile attribute, ${tilingSchemeName}.`;
- metadataError = TileProviderError.handleError(
- metadataError,
- this,
- this.errorEvent,
- message,
- undefined,
- undefined,
- undefined,
- requestMetadata
- );
- if (!metadataError.retry) {
- deferred.reject(new RuntimeError(message));
- }
- this._metadataError = metadataError;
- return;
- }
- }
- // rectangle handling
- let rectangle = Rectangle.clone(options.rectangle);
- if (!defined(rectangle)) {
- let sw;
- let ne;
- let swXY;
- let neXY;
- // In older versions of gdal x and y values were flipped, which is why we check for an option to flip
- // the values here as well. Unfortunately there is no way to autodetect whether flipping is needed.
- const flipXY = defaultValue(options.flipXY, false);
- if (flipXY) {
- swXY = new Cartesian2(
- parseFloat(bbox.getAttribute("miny")),
- parseFloat(bbox.getAttribute("minx"))
- );
- neXY = new Cartesian2(
- parseFloat(bbox.getAttribute("maxy")),
- parseFloat(bbox.getAttribute("maxx"))
- );
- } else {
- swXY = new Cartesian2(
- parseFloat(bbox.getAttribute("minx")),
- parseFloat(bbox.getAttribute("miny"))
- );
- neXY = new Cartesian2(
- parseFloat(bbox.getAttribute("maxx")),
- parseFloat(bbox.getAttribute("maxy"))
- );
- }
- // Determine based on the profile attribute if this tileset was generated by gdal2tiles.py, which
- // uses 'mercator' and 'geodetic' profiles, or by a tool compliant with the TMS standard, which is
- // 'global-mercator' and 'global-geodetic' profiles. In the gdal2Tiles case, X and Y are always in
- // geodetic degrees.
- const isGdal2tiles =
- tilingSchemeName === "geodetic" || tilingSchemeName === "mercator";
- if (
- tilingScheme.projection instanceof GeographicProjection ||
- isGdal2tiles
- ) {
- sw = Cartographic.fromDegrees(swXY.x, swXY.y);
- ne = Cartographic.fromDegrees(neXY.x, neXY.y);
- } else {
- const projection = tilingScheme.projection;
- sw = projection.unproject(swXY);
- ne = projection.unproject(neXY);
- }
- rectangle = new Rectangle(
- sw.longitude,
- sw.latitude,
- ne.longitude,
- ne.latitude
- );
- }
- // The rectangle must not be outside the bounds allowed by the tiling scheme.
- rectangle = confineRectangleToTilingScheme(rectangle, tilingScheme);
- // clamp our minimum detail level to something that isn't going to request a ridiculous number of tiles
- minimumLevel = calculateSafeMinimumDetailLevel(
- tilingScheme,
- rectangle,
- minimumLevel
- );
- const templateResource = this._tmsResource.getDerivedResource({
- url: `{z}/{x}/{reverseY}.${fileExtension}`,
- });
- deferred.resolve({
- url: templateResource,
- tilingScheme: tilingScheme,
- rectangle: rectangle,
- tileWidth: tileWidth,
- tileHeight: tileHeight,
- minimumLevel: minimumLevel,
- maximumLevel: maximumLevel,
- tileDiscardPolicy: options.tileDiscardPolicy,
- credit: options.credit,
- });
- };
- TileMapServiceImageryProvider.prototype._metadataFailure = function (error) {
- // Can't load XML, still allow options and defaults
- const options = this._options;
- const fileExtension = defaultValue(options.fileExtension, "png");
- const tileWidth = defaultValue(options.tileWidth, 256);
- const tileHeight = defaultValue(options.tileHeight, 256);
- const maximumLevel = options.maximumLevel;
- const tilingScheme = defined(options.tilingScheme)
- ? options.tilingScheme
- : new WebMercatorTilingScheme({ ellipsoid: options.ellipsoid });
- let rectangle = defaultValue(options.rectangle, tilingScheme.rectangle);
- // The rectangle must not be outside the bounds allowed by the tiling scheme.
- rectangle = confineRectangleToTilingScheme(rectangle, tilingScheme);
- // make sure we use a safe minimum detail level, so we don't request a ridiculous number of tiles
- const minimumLevel = calculateSafeMinimumDetailLevel(
- tilingScheme,
- rectangle,
- options.minimumLevel
- );
- const templateResource = this._tmsResource.getDerivedResource({
- url: `{z}/{x}/{reverseY}.${fileExtension}`,
- });
- this._deferred.resolve({
- url: templateResource,
- tilingScheme: tilingScheme,
- rectangle: rectangle,
- tileWidth: tileWidth,
- tileHeight: tileHeight,
- minimumLevel: minimumLevel,
- maximumLevel: maximumLevel,
- tileDiscardPolicy: options.tileDiscardPolicy,
- credit: options.credit,
- });
- };
- export default TileMapServiceImageryProvider;
|