Add support for dynamic urls from tile source. Fixes #1970

This commit is contained in:
John Reagan 2022-11-29 15:00:24 -05:00
parent c5639e32b5
commit 601160e9fb
5 changed files with 226 additions and 14 deletions

View File

@ -44,7 +44,7 @@
* coordinates. * coordinates.
* @param {Boolean} exists Is this tile a part of a sparse image? ( Also has * @param {Boolean} exists Is this tile a part of a sparse image? ( Also has
* this tile failed to load? ) * this tile failed to load? )
* @param {String} url The URL of this tile's image. * @param {String|() => String} url The URL of this tile's image or a function that returns a url.
* @param {CanvasRenderingContext2D} context2D The context2D of this tile if it * @param {CanvasRenderingContext2D} context2D The context2D of this tile if it
* is provided directly by the tile source. * is provided directly by the tile source.
* @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request . * @param {Boolean} loadWithAjax Whether this tile image should be loaded with an AJAX request .
@ -95,11 +95,13 @@ $.Tile = function(level, x, y, bounds, exists, url, context2D, loadWithAjax, aja
*/ */
this.exists = exists; this.exists = exists;
/** /**
* The URL of this tile's image. * Private property to hold string url or url retriever function.
* @member {String} url * Consumers should access via Tile.getUrl()
* @private
* @member {String|() => String} url
* @memberof OpenSeadragon.Tile# * @memberof OpenSeadragon.Tile#
*/ */
this.url = url; this._url = url;
/** /**
* Post parameters for this tile. For example, it can be an URL-encoded string * Post parameters for this tile. For example, it can be an URL-encoded string
* in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used * in k1=v1&k2=v2... format, or a JSON, or a FormData instance... or null if no POST request used
@ -269,7 +271,7 @@ $.Tile.prototype = {
_hasTransparencyChannel: function() { _hasTransparencyChannel: function() {
console.warn("Tile.prototype._hasTransparencyChannel() has been " + console.warn("Tile.prototype._hasTransparencyChannel() has been " +
"deprecated and will be removed in the future. Use TileSource.prototype.hasTransparency() instead."); "deprecated and will be removed in the future. Use TileSource.prototype.hasTransparency() instead.");
return !!this.context2D || this.url.match('.png'); return !!this.context2D || this.getUrl().match('.png');
}, },
/** /**
@ -342,6 +344,18 @@ $.Tile.prototype = {
return this.getImage(); return this.getImage();
}, },
/**
* The URL of this tile's image.
* @member {String} url
* @memberof OpenSeadragon.Tile#
* @deprecated
* @returns {String}
*/
get url() {
$.console.error("[Tile.url] property has been deprecated. Use [Tile.prototype.getUrl] instead.");
return this.getUrl();
},
/** /**
* Get the Image object for this tile. * Get the Image object for this tile.
* @returns {Image} * @returns {Image}
@ -350,6 +364,18 @@ $.Tile.prototype = {
return this.cacheImageRecord.getImage(); return this.cacheImageRecord.getImage();
}, },
/**
* Get the url string for this tile.
* @returns {String}
*/
getUrl: function() {
if (typeof this._url === 'function') {
return this._url();
}
return this._url;
},
/** /**
* Get the CanvasRenderingContext2D instance for tile image data drawn * Get the CanvasRenderingContext2D instance for tile image data drawn
* onto Canvas if enabled and available * onto Canvas if enabled and available

View File

@ -1480,7 +1480,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
bounds, bounds,
sourceBounds, sourceBounds,
exists, exists,
url, urlOrGetter,
post, post,
ajaxHeaders, ajaxHeaders,
context2D, context2D,
@ -1501,7 +1501,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
bounds = this.getTileBounds( level, x, y ); bounds = this.getTileBounds( level, x, y );
sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true ); sourceBounds = tileSource.getTileBounds( level, xMod, yMod, true );
exists = tileSource.tileExists( level, xMod, yMod ); exists = tileSource.tileExists( level, xMod, yMod );
url = tileSource.getTileUrl( level, xMod, yMod ); urlOrGetter = tileSource.getTileUrl( level, xMod, yMod );
post = tileSource.getTilePostData( level, xMod, yMod ); post = tileSource.getTilePostData( level, xMod, yMod );
// Headers are only applicable if loadTilesWithAjax is set // Headers are only applicable if loadTilesWithAjax is set
@ -1524,13 +1524,13 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
y, y,
bounds, bounds,
exists, exists,
url, urlOrGetter,
context2D, context2D,
this.loadTilesWithAjax, this.loadTilesWithAjax,
ajaxHeaders, ajaxHeaders,
sourceBounds, sourceBounds,
post, post,
tileSource.getTileHashKey(level, xMod, yMod, url, ajaxHeaders, post) tileSource.getTileHashKey(level, xMod, yMod, urlOrGetter, ajaxHeaders, post)
); );
if (this.getFlip()) { if (this.getFlip()) {
@ -1569,7 +1569,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
var _this = this; var _this = this;
tile.loading = true; tile.loading = true;
this._imageLoader.addJob({ this._imageLoader.addJob({
src: tile.url, src: tile.getUrl(),
tile: tile, tile: tile,
source: this.source, source: this.source,
postData: tile.postData, postData: tile.postData,
@ -1598,7 +1598,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
*/ */
_onTileLoad: function( tile, time, data, errorMsg, tileRequest ) { _onTileLoad: function( tile, time, data, errorMsg, tileRequest ) {
if ( !data ) { if ( !data ) {
$.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.url, errorMsg ); $.console.error( "Tile %s failed to load: %s - error: %s", tile, tile.getUrl(), errorMsg );
/** /**
* Triggered when a tile fails to load. * Triggered when a tile fails to load.
* *
@ -1624,7 +1624,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
} }
if ( time < this.lastResetTime ) { if ( time < this.lastResetTime ) {
$.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.url ); $.console.warn( "Ignoring tile %s loaded before reset: %s", tile, tile.getUrl() );
tile.loading = false; tile.loading = false;
return; return;
} }
@ -1669,7 +1669,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
tile.loading = false; tile.loading = false;
tile.loaded = true; tile.loaded = true;
tile.hasTransparency = _this.source.hasTransparency( tile.hasTransparency = _this.source.hasTransparency(
tile.context2D, tile.url, tile.ajaxHeaders, tile.postData tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData
); );
if (!tile.context2D) { if (!tile.context2D) {
_this._tileCache.cacheTile({ _this._tileCache.cacheTile({
@ -1852,7 +1852,7 @@ $.extend($.TiledImage.prototype, $.EventSource.prototype, /** @lends OpenSeadrag
useSketch = this.opacity < 1 || useSketch = this.opacity < 1 ||
(this.compositeOperation && this.compositeOperation !== 'source-over') || (this.compositeOperation && this.compositeOperation !== 'source-over') ||
(!this._isBottomItem() && (!this._isBottomItem() &&
this.source.hasTransparency(tile.context2D, tile.url, tile.ajaxHeaders, tile.postData)); this.source.hasTransparency(tile.context2D, tile.getUrl(), tile.ajaxHeaders, tile.postData));
} }
var sketchScale; var sketchScale;

View File

@ -618,6 +618,7 @@ $.TileSource.prototype = {
* @param {Number} level * @param {Number} level
* @param {Number} x * @param {Number} x
* @param {Number} y * @param {Number} y
* @returns {String|() => string} url - A string for the url or a function that returns a url string.
* @throws {Error} * @throws {Error}
*/ */
getTileUrl: function( level, x, y ) { getTileUrl: function( level, x, y ) {

View File

@ -0,0 +1,184 @@
/* global QUnit, testLog, Util */
//Testing of TileSource with getTileUrl that returns a function
(function() {
var ASSERT = null;
var DYNAMIC_URL = "";
var viewer = null;
var OriginalAjax = OpenSeadragon.makeAjaxRequest;
var OriginalTile = OpenSeadragon.Tile;
/**
* Set up shared variables for test
*/
var configure = function(assert, url) {
ASSERT = assert;
DYNAMIC_URL = url;
};
QUnit.module('TileSourceDynamicUrl', {
beforeEach: function () {
testLog.reset();
$("#qunit-fixture").html("<div id='example'></div>");
// Add test tile source to OSD
OpenSeadragon.DynamicUrlTestTileSource = function( options ) {
OpenSeadragon.TileSource.apply( this, [ options ] );
};
OpenSeadragon.extend( OpenSeadragon.DynamicUrlTestTileSource.prototype, OpenSeadragon.TileSource.prototype, {
supports: function( data, url ){
return url.indexOf('dynamic') !== -1;
},
configure: function( _data, url, postData ){
//some default data to trigger painting
return {
postData: postData,
tilesUrl: url,
fileFormat: "jpg",
sizeData: {Width: 55, Height: 55},
width: 55,
height: 55,
tileSize: 55,
tileOverlap: 55,
minLevel: 1,
maxLevel: 1,
displayRects: []
};
},
// getTileUrl return a function that must be called by Tile.getUrl
getTileUrl: function( _level, _x, _y ) {
// Assert that custom tile source is called correctly
ASSERT.ok(true, 'DynamicUrlTileSource.getTileUrl called');
return () => DYNAMIC_URL;
},
tileExists: function ( _level, _x, _y ) {
return true;
}
});
var hasCompletedImageInfoRequest = false;
OpenSeadragon.makeAjaxRequest = function( url, onSuccess, onError ) {
// Note that our preferred API is that you pass in a single object; the named
// arguments are for legacy support.
if( $.isPlainObject( url ) ){
onSuccess = url.success;
onError = url.error;
withCredentials = url.withCredentials;
headers = url.headers;
responseType = url.responseType || null;
postData = url.postData || null;
options = url; //save original stuff
url = url.url;
}
//first AJAX firing is the image info getter, second is the first tile request: can exit
if (hasCompletedImageInfoRequest) {
// Assert dynamic url from tileSource is called
ASSERT.equal(url, DYNAMIC_URL, 'Called dynamic url correctly');
viewer.close();
return null;
}
hasCompletedImageInfoRequest = true;
var request = Promise.resolve(url);
//some required properties to pass through processResponse(...)
request.responseText = "some text";
request.status = 200;
onSuccess(request);
return request;
};
// Override Tile to ensure getUrl is called successfully.
var Tile = function(...params) {
OriginalTile.apply(this, params);
};
OpenSeadragon.extend( Tile.prototype, OpenSeadragon.Tile.prototype, {
getUrl: function() {
ASSERT.ok(true, 'Tile.getUrl called');
return OriginalTile.prototype.getUrl.apply(this);
}
});
OpenSeadragon.Tile = Tile;
},
afterEach: function () {
ASSERT = null;
if (viewer && viewer.close) {
viewer.close();
}
viewer = null;
OpenSeadragon.makeAjaxRequest = OriginalAjax;
OpenSeadragon.Tile = OriginalTile;
}
});
/**
* Create viewer for test
*/
var testUrlCall = function(tileSourceUrl) {
var timeWatcher = Util.timeWatcher(ASSERT, 7000);
viewer = OpenSeadragon({
id: 'example',
prefixUrl: '/build/openseadragon/images/',
tileSources: tileSourceUrl,
loadTilesWithAjax: true,
});
var failHandler = function (event) {
testPostData(event.postData, "event: 'open-failed'");
viewer.removeHandler('open-failed', failHandler);
viewer.close();
};
viewer.addHandler('open-failed', failHandler);
var readyHandler = function (event) {
//relies on Tilesource contructor extending itself with options object
testPostData(event.postData, "event: 'ready'");
viewer.removeHandler('ready', readyHandler);
};
viewer.addHandler('ready', readyHandler);
var openHandler = function(event) {
viewer.removeHandler('open', openHandler);
ASSERT.ok(true, 'Open event was sent');
viewer.addHandler('close', closeHandler);
viewer.world.draw();
};
var closeHandler = function(event) {
viewer.removeHandler('close', closeHandler);
$('#example').empty();
ASSERT.ok(true, 'Close event was sent');
timeWatcher.done();
};
viewer.addHandler('open', openHandler);
};
// ----------
QUnit.test('TileSource.getTileUrl supports returning a function', function(assert) {
/**
* Expect 5 assertions to be called:
* 1. Open event was sent
* 2. DynamicUrlTileSource.getTileUrl called
* 3. Tile.getUrl called
* 4. Called dynamic url correctly
* 5. Close event was sent
*/
assert.expect(5);
configure(assert, 'dynamicUrl');
testUrlCall('dynamicUrl');
});
})();

View File

@ -47,6 +47,7 @@
<script src="/test/modules/ajax-tiles.js"></script> <script src="/test/modules/ajax-tiles.js"></script>
<script src="/test/modules/ajax-post-data.js"></script> <script src="/test/modules/ajax-post-data.js"></script>
<script src="/test/modules/imageloader.js"></script> <script src="/test/modules/imageloader.js"></script>
<script src="/test/modules/tilesource-dynamic-url.js"></script>
<!--The navigator tests are the slowest (for now; hopefully they can be sped up) <!--The navigator tests are the slowest (for now; hopefully they can be sped up)
so we put them last. --> so we put them last. -->
<script src="/test/modules/navigator.js"></script> <script src="/test/modules/navigator.js"></script>