diff --git a/CHANGES.md b/CHANGES.md
index 5e130ddca95..d15a438526b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -5,6 +5,7 @@
- Added `token`, `mapServerData`, and `parameters` properties to `ArcGisMapServerImageryProvider.ConstructorOptions`.
- Added `Ion.defaultTokenMessage` to customize the credit message shown on the map when using default Ion token.
- Added split terrain feature.
+- Added WMTS `GetFeatureInfo` support to `WebMapTileServiceImageryProvider`, including new constructor options and accompanying specs.
## 1.134 - 2025-10-01
diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md
index 8be2fa30647..f7c77e317e3 100644
--- a/CONTRIBUTORS.md
+++ b/CONTRIBUTORS.md
@@ -432,3 +432,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute to Cesiu
- [Pamela Augustine](https://github.com/pamelaAugustine)
- [宋时旺](https://github.com/BlockCnFuture)
- [Marco Zhan](https://github.com/marcoYxz)
+- [Shubham Sharma](https://github.com/ShubhamSharmaFAO)
diff --git a/packages/engine/Source/Scene/WebMapTileServiceImageryProvider.js b/packages/engine/Source/Scene/WebMapTileServiceImageryProvider.js
index 8c4db5a4738..9d085720b9d 100644
--- a/packages/engine/Source/Scene/WebMapTileServiceImageryProvider.js
+++ b/packages/engine/Source/Scene/WebMapTileServiceImageryProvider.js
@@ -5,9 +5,11 @@ import defined from "../Core/defined.js";
import DeveloperError from "../Core/DeveloperError.js";
import Event from "../Core/Event.js";
import Resource from "../Core/Resource.js";
+import UrlTemplateImageryProvider from "./UrlTemplateImageryProvider.js";
import WebMercatorTilingScheme from "../Core/WebMercatorTilingScheme.js";
import ImageryProvider from "./ImageryProvider.js";
import TimeDynamicImagery from "./TimeDynamicImagery.js";
+import GetFeatureInfoFormat from "./GetFeatureInfoFormat.js";
const defaultParameters = Object.freeze({
service: "WMTS",
@@ -40,6 +42,15 @@ const defaultParameters = Object.freeze({
* @property {string|string[]} [subdomains='abc'] The subdomains to use for the {s} placeholder in the URL template.
* If this parameter is a single string, each character in the string is a subdomain. If it is
* an array, each element in the array is a subdomain.
+ * @property {boolean} [enablePickFeatures=true] If true, {@link WebMapTileServiceImageryProvider#pickFeatures} will invoke
+ * the GetFeatureInfo operation on the WMTS server and return the features included in the response. If false,
+ * {@link WebMapTileServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable features)
+ * without communicating with the server. Set this property to false if you know your WMTS server does not support
+ * GetFeatureInfo or if you don't want this provider's features to be pickable.
+ * @property {GetFeatureInfoFormat[]} [getFeatureInfoFormats=WebMapTileServiceImageryProvider.DefaultGetFeatureInfoFormats] The formats
+ * in which to try WMTS GetFeatureInfo requests.
+ * @property {Resource|string} [getFeatureInfoUrl] The GetFeatureInfo URL of the WMTS service. If not specified, the value of url is used.
+ * @property {object} [getFeatureInfoParameters] Additional parameters to include in GetFeatureInfo requests. Keys are lowercased internally.
*/
/**
@@ -220,6 +231,75 @@ function WebMapTileServiceImageryProvider(options) {
} else {
this._subdomains = ["a", "b", "c"];
}
+
+ this._getFeatureInfoFormats =
+ options.getFeatureInfoFormats ??
+ WebMapTileServiceImageryProvider.DefaultGetFeatureInfoFormats;
+
+ this._enablePickFeatures = options.enablePickFeatures ?? true;
+
+ this._getFeatureInfoUrl = options.getFeatureInfoUrl ?? options.url;
+ const pickFeatureResource = Resource.createIfNeeded(this._getFeatureInfoUrl);
+
+ pickFeatureResource.setQueryParameters(
+ WebMapTileServiceImageryProvider.GetFeatureInfoDefaultParameters,
+ false,
+ );
+
+ if (defined(options.getFeatureInfoParameters)) {
+ pickFeatureResource.setQueryParameters(
+ objectToLowercase(options.getFeatureInfoParameters),
+ );
+ }
+
+ pickFeatureResource.setQueryParameters(
+ {
+ tilematrix: "{TileMatrix}",
+ tilerow: "{TileRow}",
+ tilecol: "{TileCol}",
+ tilematrixset: this._tileMatrixSetID,
+ layer: this._layer,
+ style: this._style,
+ infoformat: "{format}",
+ i: "{i}",
+ j: "{j}",
+ },
+ false,
+ );
+
+ if (defined(this._dimensions)) {
+ pickFeatureResource.setQueryParameters(this._dimensions);
+ }
+
+ pickFeatureResource.setTemplateValues(
+ {
+ layer: this._layer,
+ Layer: this._layer,
+ style: this._style,
+ Style: this._style,
+ TileMatrixSet: this._tileMatrixSetID,
+ },
+ true,
+ );
+
+ const pickFeaturesCustomTags = createPickFeaturesCustomTags(this);
+
+ this._pickFeaturesProvider = new UrlTemplateImageryProvider({
+ url: this._resource.clone(),
+ pickFeaturesUrl: pickFeatureResource,
+ tilingScheme: this._tilingScheme,
+ rectangle: this._rectangle,
+ tileWidth: this._tileWidth,
+ tileHeight: this._tileHeight,
+ minimumLevel: this._minimumLevel,
+ maximumLevel: this._maximumLevel,
+ subdomains: this._subdomains,
+ getFeatureInfoFormats: this._getFeatureInfoFormats,
+ enablePickFeatures: this._enablePickFeatures,
+ customTags: pickFeaturesCustomTags,
+ });
+
+ this.enablePickFeatures = this._enablePickFeatures;
}
function requestImage(imageryProvider, col, row, level, request, interval) {
@@ -320,6 +400,27 @@ Object.defineProperties(WebMapTileServiceImageryProvider.prototype, {
return this._tileWidth;
},
},
+ /**
+ * Gets or sets a value indicating whether feature picking is enabled. If true, {@link WebMapTileServiceImageryProvider#pickFeatures} will
+ * invoke the GetFeatureInfo service on the WMTS server and attempt to interpret the features included in the response. If false,
+ * {@link WebMapTileServiceImageryProvider#pickFeatures} will immediately return undefined (indicating no pickable
+ * features) without communicating with the server. Set this property to false if you know your data
+ * source does not support picking features or if you don't want this provider's features to be pickable.
+ * @memberof WebMapTileServiceImageryProvider.prototype
+ * @type {boolean}
+ * @default true
+ */
+ enablePickFeatures: {
+ get: function () {
+ return this._enablePickFeatures;
+ },
+ set: function (enablePickFeatures) {
+ this._enablePickFeatures = enablePickFeatures;
+ if (defined(this._pickFeaturesProvider)) {
+ this._pickFeaturesProvider.enablePickFeatures = enablePickFeatures;
+ }
+ },
+ },
/**
* Gets the height of each tile, in pixels.
@@ -434,6 +535,18 @@ Object.defineProperties(WebMapTileServiceImageryProvider.prototype, {
},
},
+ /**
+ * Gets the GetFeatureInfo URL of the WMTS server.
+ * @memberof WebMapTileServiceImageryProvider.prototype
+ * @type {Resource|string}
+ * @readonly
+ */
+ getFeatureInfoUrl: {
+ get: function () {
+ return this._getFeatureInfoUrl;
+ },
+ },
+
/**
* Gets a value indicating whether or not the images provided by this imagery provider
* include an alpha channel. If this property is false, an alpha channel, if present, will
@@ -553,15 +666,19 @@ WebMapTileServiceImageryProvider.prototype.requestImage = function (
};
/**
- * Picking features is not currently supported by this imagery provider, so this function simply returns
- * undefined.
+ * Asynchronously determines what features, if any, are located at a given longitude and latitude within
+ * a tile. This function should not be called before {@link ImageryProvider#ready} returns true.
*
* @param {number} x The tile X coordinate.
* @param {number} y The tile Y coordinate.
* @param {number} level The tile level.
* @param {number} longitude The longitude at which to pick features.
* @param {number} latitude The latitude at which to pick features.
- * @return {undefined} Undefined since picking is not supported.
+ * @return {Promise|undefined} A promise for the picked features that will resolve when the asynchronous
+ * picking completes. The resolved value is an array of {@link ImageryLayerFeatureInfo}
+ * instances. The array may be empty if no features are found at the given location.
+ *
+ * @exception {DeveloperError} pickFeatures must not be called before the imagery provider is ready.
*/
WebMapTileServiceImageryProvider.prototype.pickFeatures = function (
x,
@@ -570,6 +687,91 @@ WebMapTileServiceImageryProvider.prototype.pickFeatures = function (
longitude,
latitude,
) {
- return undefined;
+ if (
+ !this.enablePickFeatures ||
+ this._getFeatureInfoFormats.length === 0 ||
+ !defined(this._pickFeaturesProvider)
+ ) {
+ return undefined;
+ }
+
+ const pickFeaturesProvider = this._pickFeaturesProvider;
+ pickFeaturesProvider.enablePickFeatures = this.enablePickFeatures;
+
+ const pickResource = pickFeaturesProvider._pickFeaturesResource;
+
+ if (defined(this._dimensions)) {
+ if (this._useKvp) {
+ pickResource.setQueryParameters(this._dimensions);
+ } else {
+ pickResource.setTemplateValues(this._dimensions);
+ }
+ }
+
+ const timeDynamicImagery = this._timeDynamicImagery;
+ if (defined(timeDynamicImagery)) {
+ const currentInterval = timeDynamicImagery.currentInterval;
+ if (defined(currentInterval) && defined(currentInterval.data)) {
+ if (this._useKvp) {
+ pickResource.setQueryParameters(currentInterval.data);
+ } else {
+ pickResource.setTemplateValues(currentInterval.data);
+ }
+ }
+ }
+
+ return pickFeaturesProvider.pickFeatures(x, y, level, longitude, latitude);
};
+
+WebMapTileServiceImageryProvider.GetFeatureInfoDefaultParameters =
+ Object.freeze({
+ service: "WMTS",
+ version: "1.0.0",
+ request: "GetFeatureInfo",
+ });
+
+WebMapTileServiceImageryProvider.DefaultGetFeatureInfoFormats = Object.freeze([
+ Object.freeze(new GetFeatureInfoFormat("json", "application/json")),
+ Object.freeze(new GetFeatureInfoFormat("xml", "text/xml")),
+ Object.freeze(new GetFeatureInfoFormat("text", "text/html")),
+]);
+
+function createPickFeaturesCustomTags(imageryProvider) {
+ function getTileMatrix(level) {
+ const labels = imageryProvider._tileMatrixLabels;
+ return defined(labels) ? labels[level] : level.toString();
+ }
+
+ return {
+ TileMatrix: function (provider, x, y, level) {
+ return getTileMatrix(level);
+ },
+ tilematrix: function (provider, x, y, level) {
+ return getTileMatrix(level);
+ },
+ TileRow: function (provider, x, y) {
+ return y.toString();
+ },
+ tilerow: function (provider, x, y) {
+ return y.toString();
+ },
+ TileCol: function (provider, x, y) {
+ return x.toString();
+ },
+ tilecol: function (provider, x, y) {
+ return x.toString();
+ },
+ };
+}
+
+function objectToLowercase(obj) {
+ const result = {};
+ for (const key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ result[key.toLowerCase()] = obj[key];
+ }
+ }
+ return result;
+}
+
export default WebMapTileServiceImageryProvider;
diff --git a/packages/engine/Specs/Scene/WebMapTileServiceImageryProviderSpec.js b/packages/engine/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
index f16abebc577..a0d43cbbbea 100644
--- a/packages/engine/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
+++ b/packages/engine/Specs/Scene/WebMapTileServiceImageryProviderSpec.js
@@ -6,9 +6,11 @@ import {
GeographicTilingScheme,
Imagery,
ImageryLayer,
+ ImageryLayerFeatureInfo,
ImageryProvider,
ImageryState,
JulianDate,
+ GetFeatureInfoFormat,
objectToQuery,
queryToObject,
Request,
@@ -295,9 +297,10 @@ describe("Scene/WebMapTileServiceImageryProvider", function () {
expect(provider.proxy).toBeUndefined();
});
- // non default parameters values
it("uses parameters passed to constructor", function () {
- const tilingScheme = new GeographicTilingScheme();
+ const tilingScheme = new GeographicTilingScheme({
+ numberOfLevelZeroTilesX: 1,
+ });
const rectangle = new WebMercatorTilingScheme().rectangle;
const provider = new WebMapTileServiceImageryProvider({
layer: "someLayer",
@@ -722,4 +725,411 @@ describe("Scene/WebMapTileServiceImageryProvider", function () {
expect(lastUrl).toEqual(uri.toString());
});
});
+
+ it("returns undefined if getFeatureInfoFormats is empty", function () {
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ getFeatureInfoFormats: [],
+ });
+
+ expect(provider.pickFeatures(0, 0, 0, 0.0, 0.0)).toBeUndefined();
+ });
+
+ it("returns undefined if enablePickFeatures is false", function () {
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ });
+
+ provider.enablePickFeatures = false;
+ expect(provider.enablePickFeatures).toBe(false);
+ expect(provider.pickFeatures(0, 0, 0, 0.0, 0.0)).toBeUndefined();
+ });
+
+ it("initializes GetFeatureInfo picking wiring (KVP)", function () {
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ });
+
+ expect(provider._resource).toBeDefined();
+ expect(provider._layer).toBeDefined();
+ expect(provider._style).toBeDefined();
+ expect(provider._tileMatrixSetID).toBeDefined();
+ expect(provider._pickFeaturesProvider).toBeDefined();
+ expect(provider._pickFeaturesProvider._pickFeaturesResource).toBeDefined();
+ expect(
+ provider._getFeatureInfoFormats && provider._getFeatureInfoFormats.length,
+ ).toBeGreaterThan(0);
+ expect(provider.enablePickFeatures).toBe(true);
+ expect(provider.getFeatureInfoUrl).toBeDefined();
+ });
+
+ it("initializes GetFeatureInfo picking wiring (REST template)", function () {
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid/{Style}/{TileMatrixSet}/{TileMatrix}/{TileRow}/{TileCol}.png",
+ tileMatrixSetID: "someTMS",
+ });
+
+ expect(provider._resource).toBeDefined();
+ expect(provider._layer).toBeDefined();
+ expect(provider._style).toBeDefined();
+ expect(provider._tileMatrixSetID).toBeDefined();
+ expect(provider._pickFeaturesProvider).toBeDefined();
+ expect(provider._pickFeaturesProvider._pickFeaturesResource).toBeDefined();
+ expect(
+ provider._getFeatureInfoFormats && provider._getFeatureInfoFormats.length,
+ ).toBeGreaterThan(0);
+ expect(provider.enablePickFeatures).toBe(true);
+ expect(provider.getFeatureInfoUrl).toBeDefined();
+ });
+
+ it("does not return undefined when enablePickFeatures is toggled to true", function () {
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ });
+
+ provider.enablePickFeatures = false;
+
+ spyOn(Resource.prototype, "fetchJson").and.returnValue(
+ Promise.resolve({
+ type: "FeatureCollection",
+ features: [],
+ }),
+ );
+
+ provider.enablePickFeatures = true;
+ expect(provider.enablePickFeatures).toBe(true);
+
+ const pickPromise = provider.pickFeatures(0, 0, 0, 0.0, 0.0);
+ expect(pickPromise).toBeDefined();
+
+ return pickPromise.then(function (features) {
+ expect(Array.isArray(features)).toBe(true);
+ expect(features.length).toBe(0);
+ });
+ });
+
+ it("builds GetFeatureInfo request and parses JSON response", function () {
+ const tilingScheme = new GeographicTilingScheme({
+ numberOfLevelZeroTilesX: 1,
+ });
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ tileMatrixLabels: ["level-zero"],
+ tilingScheme: tilingScheme,
+ format: "image/png",
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ const url = this.getUrlComponent(true);
+ const uri = new Uri(url);
+ const params = queryToObject(uri.query());
+ expect(params.request).toBe("GetFeatureInfo");
+ expect(params.service).toBe("WMTS");
+ expect(params.version).toBe("1.0.0");
+ expect(params.layer).toBe("someLayer");
+ expect(params.style).toBe("someStyle");
+ expect(params.tilematrixset).toBe("someTMS");
+ expect(params.tilematrix).toBe("level-zero");
+ expect(params.tilecol).toBe("0");
+ expect(params.tilerow).toBe("0");
+ expect(params.i).toBe("128");
+ expect(params.j).toBe("128");
+ expect(params.infoformat).toBe("application/json");
+
+ return Promise.resolve({
+ type: "FeatureCollection",
+ features: [
+ {
+ type: "Feature",
+ geometry: {
+ type: "Point",
+ coordinates: [0, 0],
+ },
+ properties: {
+ name: "Feature name",
+ },
+ },
+ ],
+ });
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function (features) {
+ expect(Resource.prototype.fetchJson).toHaveBeenCalled();
+ expect(features.length).toBe(1);
+
+ const featureInfo = features[0];
+ expect(featureInfo).toBeInstanceOf(ImageryLayerFeatureInfo);
+ expect(featureInfo.name).toBe("Feature name");
+ expect(featureInfo.position.longitude).toBeCloseTo(0, 10);
+ expect(featureInfo.position.latitude).toBeCloseTo(0, 10);
+ });
+ });
+
+ it("uses getFeatureInfoUrl in options for picking", function () {
+ const featureUrl = "http://wmts.invalid/feature";
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ getFeatureInfoUrl: featureUrl,
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ const url = this.getUrlComponent(true);
+ expect(url.indexOf(featureUrl)).toBe(0);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function (features) {
+ expect(Array.isArray(features)).toBe(true);
+ });
+ });
+
+ it("includes dimensions in GetFeatureInfo KVP requests", function () {
+ let capturedUrl;
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ capturedUrl = this.getUrlComponent(true);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid/kvp",
+ tileMatrixSetID: "someTMS",
+ dimensions: { FOO: "BAR" },
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function () {
+ const uri = new Uri(capturedUrl);
+ const params = queryToObject(uri.query());
+ expect(params.FOO).toBe("BAR");
+ });
+ });
+
+ it("includes dimensions in GetFeatureInfo RESTful requests via template values", function () {
+ let capturedUrl;
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ capturedUrl = this.getUrlComponent(true);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid/{FOO}",
+ tileMatrixSetID: "someTMS",
+ dimensions: { FOO: "BAR" },
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function () {
+ expect(capturedUrl).toStartWith("http://wmts.invalid/BAR");
+ });
+ });
+
+ it("includes time-dynamic template values in GetFeatureInfo RESTful requests", function () {
+ let capturedUrl;
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ capturedUrl = this.getUrlComponent(true);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ const times = TimeIntervalCollection.fromIso8601({
+ iso8601: "2017-04-26/2017-04-30/P1D",
+ dataCallback: function (interval, index) {
+ return { Time: JulianDate.toIso8601(interval.start) };
+ },
+ });
+ const clock = new Clock({
+ currentTime: JulianDate.fromIso8601("2017-04-26"),
+ shouldAnimate: false,
+ });
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid/{Time}",
+ tileMatrixSetID: "someTMS",
+ clock: clock,
+ times: times,
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function () {
+ expect(decodeURIComponent(capturedUrl)).toContain(
+ "/2017-04-26T00:00:00Z",
+ );
+ });
+ });
+
+ it("applies getFeatureInfoParameters with lowercased keys", function () {
+ let capturedUrl;
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ capturedUrl = this.getUrlComponent(true);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ getFeatureInfoParameters: { CUSTOM_PARAM: "foo" },
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function () {
+ const uri = new Uri(capturedUrl);
+ const params = queryToObject(uri.query());
+ expect(params.custom_param).toBe("foo");
+ });
+ });
+
+ it("includes time-dynamic parameters in GetFeatureInfo requests", function () {
+ let capturedUrl;
+ spyOn(Resource.prototype, "fetchJson").and.callFake(function () {
+ capturedUrl = this.getUrlComponent(true);
+ return Promise.resolve({ type: "FeatureCollection", features: [] });
+ });
+
+ const times = TimeIntervalCollection.fromIso8601({
+ iso8601: "2017-04-26/2017-04-30/P1D",
+ dataCallback: function (interval, index) {
+ return { Time: JulianDate.toIso8601(interval.start) };
+ },
+ });
+ const clock = new Clock({
+ currentTime: JulianDate.fromIso8601("2017-04-26"),
+ shouldAnimate: false,
+ });
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ clock: clock,
+ times: times,
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("json")],
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function () {
+ const uri = new Uri(capturedUrl);
+ const params = queryToObject(uri.query());
+ expect(params.Time).toBe("2017-04-26T00:00:00Z");
+ });
+ });
+
+ it("sets correct infoformat for XML and HTML requests", function () {
+ const providerXml = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("xml")],
+ });
+
+ spyOn(Resource.prototype, "fetchXML").and.callFake(function () {
+ const url = this.getUrlComponent(true);
+ const uri = new Uri(url);
+ const params = queryToObject(uri.query());
+ expect(params.infoformat).toBe("text/xml");
+ const parser = new DOMParser();
+ return Promise.resolve(
+ parser.parseFromString("", "application/xml"),
+ );
+ });
+
+ const providerHtml = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ getFeatureInfoFormats: [new GetFeatureInfoFormat("html")],
+ });
+
+ spyOn(Resource.prototype, "fetchText").and.callFake(function () {
+ const url = this.getUrlComponent(true);
+ const uri = new Uri(url);
+ const params = queryToObject(uri.query());
+ expect(params.infoformat).toBe("text/html");
+ return Promise.resolve("x");
+ });
+
+ return providerXml
+ .pickFeatures(0, 0, 0, 0.0, 0.0)
+ .then(function () {
+ return providerHtml.pickFeatures(0, 0, 0, 0.0, 0.0);
+ })
+ .then(function () {
+ expect(true).toBe(true);
+ });
+ });
+
+ it("falls back to the next format when the first request fails", function () {
+ function fallbackProcessor(response) {
+ expect(response.custom).toBe(true);
+ const feature = new ImageryLayerFeatureInfo();
+ feature.name = "Fallback feature";
+ return [feature];
+ }
+
+ const formats = [
+ new GetFeatureInfoFormat("json"),
+ new GetFeatureInfoFormat("foo", "application/foo", fallbackProcessor),
+ ];
+
+ const provider = new WebMapTileServiceImageryProvider({
+ layer: "someLayer",
+ style: "someStyle",
+ url: "http://wmts.invalid",
+ tileMatrixSetID: "someTMS",
+ tilingScheme: new GeographicTilingScheme(),
+ getFeatureInfoFormats: formats,
+ });
+
+ spyOn(Resource.prototype, "fetchJson").and.returnValue(
+ Promise.reject(new Error("no json")),
+ );
+
+ spyOn(Resource.prototype, "fetch").and.callFake(function (options) {
+ const url = (options && options.url) || this.getUrlComponent(true);
+ const uri = new Uri(url);
+ const params = queryToObject(uri.query());
+ expect(params.infoformat).toBe("application/foo");
+ expect(options.responseType).toBe("application/foo");
+ return Promise.resolve({
+ custom: true,
+ });
+ });
+
+ return provider.pickFeatures(0, 0, 0, 0.0, 0.0).then(function (features) {
+ expect(Resource.prototype.fetchJson).toHaveBeenCalled();
+ expect(Resource.prototype.fetch).toHaveBeenCalled();
+ expect(features.length).toBe(1);
+ expect(features[0].name).toBe("Fallback feature");
+ });
+ });
});