Merge pull request #9430 from lutzhelm/iiif

Adds IIIF Image API tile source
This commit is contained in:
Tim Schaub
2019-05-10 06:32:28 -06:00
committed by GitHub
10 changed files with 1657 additions and 0 deletions
+8
View File
@@ -220,3 +220,11 @@ Duplicate item added to a unique collection. For example, it may be that you tr
### 59 ### 59
Invalid command found in the PBF. This indicates that the loaded vector tile may be corrupt. Invalid command found in the PBF. This indicates that the loaded vector tile may be corrupt.
### 60
Missing or invalid `size`.
### 61
Cannot determine IIIF Image API version from provided image information JSON.
+16
View File
@@ -0,0 +1,16 @@
---
layout: example.html
title: IIIF Image API
shortdesc: Example of a IIIF Image API source.
docs: >
Example of a tile source for an International Image Interoperability Framework (IIIF) Image Service.
Try any Image API version 1 or 2 service.
tags: "IIIF, IIIF Image API, tile source"
---
<div id="map" class="map"></div>
<div class="controls">
<div id="iiif-notification">&nbsp;</div>
Enter <code>info.json</code> URL:
<input type="text" id="imageInfoUrl" value="https://iiif.ub.uni-leipzig.de/iiif/j2k/0000/0107/0000010732/00000072.jpx/info.json">
<button id="display">Display image</button>
</div>
+46
View File
@@ -0,0 +1,46 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js';
import IIIF from '../src/ol/source/IIIF.js';
import IIIFInfo from '../src/ol/format/IIIFInfo.js';
const layer = new TileLayer(),
map = new Map({
layers: [layer],
target: 'map'
}),
notifyDiv = document.getElementById('iiif-notification'),
urlInput = document.getElementById('imageInfoUrl'),
displayButton = document.getElementById('display');
function refreshMap(imageInfoUrl) {
fetch(imageInfoUrl).then(function(response) {
response.json().then(function(imageInfo) {
const options = new IIIFInfo(imageInfo).getTileSourceOptions();
if (options === undefined || options.version === undefined) {
notifyDiv.textContent = 'Data seems to be no valid IIIF image information.';
return;
}
options.zDirection = -1;
const iiifTileSource = new IIIF(options);
layer.setSource(iiifTileSource);
map.setView(new View({
resolutions: iiifTileSource.getTileGrid().getResolutions(),
extent: iiifTileSource.getTileGrid().getExtent(),
constrainOnlyCenter: true
}));
map.getView().fit(iiifTileSource.getTileGrid().getExtent());
notifyDiv.textContent = '';
}).catch(function(body) {
notifyDiv.textContent = 'Could not read image info json. ' + body;
});
}).catch(function() {
notifyDiv.textContent = 'Could not read data from URL.';
});
}
displayButton.addEventListener('click', function() {
refreshMap(urlInput.value);
});
refreshMap(urlInput.value);
+1
View File
@@ -7,6 +7,7 @@ export {default as GeoJSON} from './format/GeoJSON.js';
export {default as GML} from './format/GML.js'; export {default as GML} from './format/GML.js';
export {default as GPX} from './format/GPX.js'; export {default as GPX} from './format/GPX.js';
export {default as IGC} from './format/IGC.js'; export {default as IGC} from './format/IGC.js';
export {default as IIIFInfo} from './format/IIIFInfo.js';
export {default as KML} from './format/KML.js'; export {default as KML} from './format/KML.js';
export {default as MVT} from './format/MVT.js'; export {default as MVT} from './format/MVT.js';
export {default as OWS} from './format/OWS.js'; export {default as OWS} from './format/OWS.js';
+426
View File
@@ -0,0 +1,426 @@
/**
* @module ol/format/IIIFInfo
*/
import {assert} from '../asserts.js';
/**
* @typedef {Object} PreferredOptions
* @property {string} [format] Preferred image format. Will be used if the image information
* indicates support for that format.
* @property {string} [quality] IIIF image qualitiy. Will be used if the image information
* indicates support for that quality.
*/
/**
* @typedef {Object} SupportedFeatures
* @property {Array<string>} [supports] Supported IIIF image size and region
* calculation features.
* @property {Array<string>} [formats] Supported image formats.
* @property {Array<string>} [qualities] Supported IIIF image qualities.
*/
/**
* @typedef {Object} ImageInformationResponse1_0
* @property {string} identifier
* @property {number} width
* @property {number} height
* @property {Array<number>} [scale_factors] Resolution scaling factors.
* @property {number} [tile_width]
* @property {number} [tile_height]
* @property {Array<string>} [formats] Supported image formats.
* @property {string} [profile] Compliance level URI.
*/
/**
* @typedef {Object} ImageInformationResponse1_1
* @property {string} "@id" The base URI of the image service.
* @property {string} "@context" JSON-LD context URI.
* @property {number} width Full image width.
* @property {number} height Full image height.
* @property {Array<number>} [scale_factors] Resolution scaling factors.
* @property {number} [tile_width]
* @property {number} [tile_height]
* @property {Array<string>} [formats] Supported image formats.
* @property {string} [profile] Compliance level URI.
*/
/**
* @typedef {Object} TileInfo
* @property {Array<number>} scaleFactors Supported resolution scaling factors.
* @property {number} width Tile width in pixels.
* @property {number} [height] Tile height in pixels. Same as tile width if height is
* not given.
*/
/**
* @typedef {Object} IiifProfile
* @property {Array<string>} [formats] Supported image formats for the image service.
* @property {Array<string>} [qualities] Supported IIIF image qualities.
* @property {Array<string>} [supports] Supported features.
* @property {number} [maxArea] Maximum area (pixels) available for this image service.
* @property {number} [maxHeight] Maximum height.
* @property {number} [maxWidth] Maximum width.
*/
/**
* @typedef {Object} ImageInformationResponse2
* @property {string} "@id" The base URI of the image service.
* @property {string} "@context" JSON-LD context IRI
* @property {number} width Full image width.
* @property {number} height Full image height.
* @property {Array<string|IiifProfile>} profile Additional informations about the image
* service's capabilities.
* @property {Array<Object<string, number>>} [sizes] Supported full image dimensions.
* @property {Array<TileInfo>} [tiles] Supported tile sizes and resolution scaling factors.
*/
/**
* @typedef {Object} ImageInformationResponse3
* @property {string} id The base URI of the image service.
* @property {string} "@context" JSON-LD context IRI
* @property {number} width Full image width.
* @property {number} height Full image height.
* @property {string} profile Compliance level, one of 'level0', 'level1' or 'level2'
* @property {Array<Object<string, number>>} [sizes] Supported full image dimensions.
* @property {Array<TileInfo>} [tiles] Supported tile sizes and resolution scaling factors.
* @property {number} [maxArea] Maximum area (pixels) available for this image service.
* @property {number} [maxHeight] Maximum height.
* @property {number} [maxWidth] Maximum width.
* @property {Array<string>} [extraQualities] IIIF image qualities supported by the
* image service additional to the ones indicated by the compliance level.
* @property {Array<string>} [extraFormats] Image formats supported by the
* image service additional to the ones indicated by the compliance level.
* @property {Array<string>} [extraFeatures] Additional supported features whose support
* is not indicated by the compliance level.
*/
/**
* @enum {string}
*/
export const Versions = {
VERSION1: 'version1',
VERSION2: 'version2',
VERSION3: 'version3'
};
/**
* Supported image formats, qualities and supported region / size calculation features
* for different image API versions and compliance levels
* @const
* @type {Object<string, Object<string, SupportedFeatures>>}
*/
const IIIF_PROFILE_VALUES = {};
IIIF_PROFILE_VALUES[Versions.VERSION1] = {
'level0': {
supports: [],
formats: [],
qualities: ['native']
},
'level1': {
supports: ['regionByPx', 'sizeByW', 'sizeByH', 'sizeByPct'],
formats: ['jpg'],
qualities: ['native']
},
'level2': {
supports: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByPct',
'sizeByConfinedWh', 'sizeByWh'],
formats: ['jpg', 'png'],
qualities: ['native', 'color', 'grey', 'bitonal']
}
};
IIIF_PROFILE_VALUES[Versions.VERSION2] = {
'level0': {
supports: [],
formats: ['jpg'],
qualities: ['default']
},
'level1': {
supports: ['regionByPx', 'sizeByW', 'sizeByH', 'sizeByPct'],
formats: ['jpg'],
qualities: ['default']
},
'level2': {
supports: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByPct',
'sizeByConfinedWh', 'sizeByDistortedWh', 'sizeByWh'],
formats: ['jpg', 'png'],
qualities: ['default', 'bitonal']
}
};
IIIF_PROFILE_VALUES[Versions.VERSION3] = {
'level0': {
supports: [],
formats: ['jpg'],
qualities: ['default']
},
'level1': {
supports: ['regionByPx', 'regionSquare', 'sizeByW', 'sizeByH'],
formats: ['jpg'],
qualities: ['default']
},
'level2': {
supports: ['regionByPx', 'regionSquare', 'regionByPct',
'sizeByW', 'sizeByH', 'sizeByPct', 'sizeByConfinedWh', 'sizeByWh'],
formats: ['jpg'],
qualities: ['default', 'bitonal']
}
};
IIIF_PROFILE_VALUES['none'] = {
'none': {
supports: [],
formats: [],
qualities: []
}
};
const COMPLIANCE_VERSION1 = new RegExp('^https?\:\/\/library\.stanford\.edu\/iiif\/image-api\/(1\.1\/)?compliance\.html#level[0-2]$');
const COMPLIANCE_VERSION2 = new RegExp('^https?\:\/\/iiif\.io\/api\/image\/2\/level[0-2](\.json)?$');
const COMPLIANCE_VERSION3 = new RegExp('(^https?\:\/\/iiif\.io\/api\/image\/3\/level[0-2](\.json)?$)|(^level[0-2]$)');
function generateVersion1Options(iiifInfo) {
let levelProfile = iiifInfo.getComplianceLevelSupportedFeatures();
// Version 1.0 and 1.1 do not require a profile.
if (levelProfile === undefined) {
levelProfile = IIIF_PROFILE_VALUES[Versions.VERSION1]['level0'];
}
return {
url: iiifInfo.imageInfo['@id'] === undefined ? undefined : iiifInfo.imageInfo['@id'].replace(/\/?(info.json)?$/g, ''),
supports: levelProfile.supports,
formats: [...levelProfile.formats, iiifInfo.imageInfo.formats === undefined ?
[] : iiifInfo.imageInfo.formats
],
qualities: [...levelProfile.qualities, iiifInfo.imageInfo.qualities === undefined ?
[] : iiifInfo.imageInfo.qualities
],
resolutions: iiifInfo.imageInfo.scale_factors,
tileSize: iiifInfo.imageInfo.tile_width !== undefined ? (iiifInfo.imageInfo.tile_height !== undefined ?
[iiifInfo.imageInfo.tile_width, iiifInfo.imageInfo.tile_height] : [iiifInfo.imageInfo.tile_width, iiifInfo.imageInfo.tile_width]) :
(iiifInfo.imageInfo.tile_height != undefined ? [iiifInfo.imageInfo.tile_height, iiifInfo.imageInfo.tile_height] : undefined)
};
}
function generateVersion2Options(iiifInfo) {
const levelProfile = iiifInfo.getComplianceLevelSupportedFeatures(),
additionalProfile = Array.isArray(iiifInfo.imageInfo.profile) && iiifInfo.imageInfo.profile.length > 1,
profileSupports = additionalProfile && iiifInfo.imageInfo.profile[1].supports ? iiifInfo.imageInfo.profile[1].supports : [],
profileFormats = additionalProfile && iiifInfo.imageInfo.profile[1].formats ? iiifInfo.imageInfo.profile[1].formats : [],
profileQualities = additionalProfile && iiifInfo.imageInfo.profile[1].qualities ? iiifInfo.imageInfo.profile[1].qualities : [];
return {
url: iiifInfo.imageInfo['@id'].replace(/\/?(info.json)?$/g, ''),
sizes: iiifInfo.imageInfo.sizes === undefined ? undefined : iiifInfo.imageInfo.sizes.map(function(size) {
return [size.width, size.height];
}),
tileSize: iiifInfo.imageInfo.tiles === undefined ? undefined : [
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.width;
})[0],
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.height === undefined ? tile.width : tile.height;
})[0]
],
resolutions: iiifInfo.imageInfo.tiles === undefined ? undefined :
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.scaleFactors;
})[0],
supports: [...levelProfile.supports, ...profileSupports],
formats: [...levelProfile.formats, ...profileFormats],
qualities: [...levelProfile.qualities, ...profileQualities]
};
}
function generateVersion3Options(iiifInfo) {
const levelProfile = iiifInfo.getComplianceLevelSupportedFeatures();
return {
url: iiifInfo.imageInfo['id'],
sizes: iiifInfo.imageInfo.sizes === undefined ? undefined : iiifInfo.imageInfo.sizes.map(function(size) {
return [size.width, size.height];
}),
tileSize: iiifInfo.imageInfo.tiles === undefined ? undefined : [
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.width;
})[0],
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.height;
})[0]
],
resolutions: iiifInfo.imageInfo.tiles === undefined ? undefined :
iiifInfo.imageInfo.tiles.map(function(tile) {
return tile.scaleFactors;
})[0],
supports: iiifInfo.imageInfo.extraFeatures === undefined ? levelProfile.supports :
[...levelProfile.supports, ...iiifInfo.imageInfo.extraFeatures],
formats: iiifInfo.imageInfo.extraFormats === undefined ? levelProfile.formats :
[...levelProfile.formats, ...iiifInfo.imageInfo.extraFormats],
qualities: iiifInfo.imageInfo.extraQualities === undefined ? levelProfile.qualities :
[...levelProfile.supports, ...iiifInfo.imageInfo.extraQualities],
maxWidth: undefined,
maxHeight: undefined,
maxArea: undefined
};
}
const versionFunctions = {};
versionFunctions[Versions.VERSION1] = generateVersion1Options;
versionFunctions[Versions.VERSION2] = generateVersion2Options;
versionFunctions[Versions.VERSION3] = generateVersion3Options;
/**
* @classdesc
* Format for transforming IIIF Image API image information responses into
* IIIF tile source ready options
*
* @api
*/
class IIIFInfo {
/**
* @param {ImageInformationResponse1_0|ImageInformationResponse1_1|ImageInformationResponse2|ImageInformationResponse3|string} imageInfo
* Deserialized image information JSON response object or JSON response as string
*/
constructor(imageInfo) {
this.setImageInfo(imageInfo);
}
/**
* @param {Object|string} imageInfo Deserialized image information JSON response
* object or JSON response as string
*/
setImageInfo(imageInfo) {
if (typeof imageInfo == 'string') {
this.imageInfo = JSON.parse(imageInfo);
} else {
this.imageInfo = imageInfo;
}
}
/**
* @returns {Versions} Major IIIF version.
*/
getImageApiVersion() {
if (this.imageInfo === undefined) {
return;
}
let context = this.imageInfo['@context'] || 'ol-no-context';
if (typeof context == 'string') {
context = [context];
}
for (let i = 0; i < context.length; i++) {
switch (context[i]) {
case 'http://library.stanford.edu/iiif/image-api/1.1/context.json':
case 'http://iiif.io/api/image/1/context.json':
return Versions.VERSION1;
case 'http://iiif.io/api/image/2/context.json':
return Versions.VERSION2;
case 'http://iiif.io/api/image/3/context.json':
return Versions.VERSION3;
case 'ol-no-context':
// Image API 1.0 has no '@context'
if (this.getComplianceLevelEntryFromProfile(Versions.VERSION1) && this.imageInfo.identifier) {
return Versions.VERSION1;
}
break;
default:
}
}
assert(false, 61);
}
/**
* @param {Versions} version Optional IIIF image API version
* @returns {string} Compliance level as it appears in the IIIF image information
* response.
*/
getComplianceLevelEntryFromProfile(version) {
if (this.imageInfo === undefined || this.imageInfo.profile === undefined) {
return;
}
if (version === undefined) {
version = this.getImageApiVersion();
}
switch (version) {
case Versions.VERSION1:
if (COMPLIANCE_VERSION1.test(this.imageInfo.profile)) {
return this.imageInfo.profile;
}
break;
case Versions.VERSION3:
if (COMPLIANCE_VERSION3.test(this.imageInfo.profile)) {
return this.imageInfo.profile;
}
break;
case Versions.VERSION2:
if (typeof this.imageInfo.profile === 'string' && COMPLIANCE_VERSION2.test(this.imageInfo.profile)) {
return this.imageInfo.profile;
}
if (Array.isArray(this.imageInfo.profile) && this.imageInfo.profile.length > 0
&& typeof this.imageInfo.profile[0] === 'string' && COMPLIANCE_VERSION2.test(this.imageInfo.profile[0])) {
return this.imageInfo.profile[0];
}
break;
default:
}
}
/**
* @param {Versions} version Optional IIIF image API version
* @returns {string} Compliance level, on of 'level0', 'level1' or 'level2' or undefined
*/
getComplianceLevelFromProfile(version) {
const complianceLevel = this.getComplianceLevelEntryFromProfile(version);
if (complianceLevel === undefined) {
return undefined;
}
const level = complianceLevel.match(/level[0-2](\.json)?$/g);
return Array.isArray(level) ? level[0].replace('.json', '') : undefined;
}
/**
* @returns {SupportedFeatures} Image formats, qualities and region / size calculation
* methods that are supported by the IIIF service.
*/
getComplianceLevelSupportedFeatures() {
if (this.imageInfo === undefined) {
return;
}
const version = this.getImageApiVersion();
const level = this.getComplianceLevelFromProfile(version);
if (level === undefined) {
return IIIF_PROFILE_VALUES['none']['none'];
}
return IIIF_PROFILE_VALUES[version][level];
}
/**
* @param {PreferredOptions} opt_preferredOptions Optional options for preferred format and quality.
* @returns {import("../source/IIIF.js").Options} IIIF tile source ready constructor options.
*/
getTileSourceOptions(opt_preferredOptions) {
const options = opt_preferredOptions || {},
version = this.getImageApiVersion();
if (version === undefined) {
return;
}
const imageOptions = version === undefined ? undefined : versionFunctions[version](this);
if (imageOptions === undefined) {
return;
}
return {
url: imageOptions.url,
version: version,
size: [this.imageInfo.width, this.imageInfo.height],
sizes: imageOptions.sizes,
format: imageOptions.formats.includes(options.format) ? options.format : 'jpg',
supports: imageOptions.supports,
quality: options.quality && imageOptions.qualities.includes(options.quality) ?
options.quality : imageOptions.qualities.includes('native') ? 'native' : 'default',
resolutions: Array.isArray(imageOptions.resolutions) ? imageOptions.resolutions.sort(function(a, b) {
return b - a;
}) : undefined,
tileSize: imageOptions.tileSize
};
}
}
export default IIIFInfo;
+1
View File
@@ -5,6 +5,7 @@
export {default as BingMaps} from './source/BingMaps.js'; export {default as BingMaps} from './source/BingMaps.js';
export {default as CartoDB} from './source/CartoDB.js'; export {default as CartoDB} from './source/CartoDB.js';
export {default as Cluster} from './source/Cluster.js'; export {default as Cluster} from './source/Cluster.js';
export {default as IIIF} from './source/IIIF.js';
export {default as Image} from './source/Image.js'; export {default as Image} from './source/Image.js';
export {default as ImageArcGISRest} from './source/ImageArcGISRest.js'; export {default as ImageArcGISRest} from './source/ImageArcGISRest.js';
export {default as ImageCanvas} from './source/ImageCanvas.js'; export {default as ImageCanvas} from './source/ImageCanvas.js';
+284
View File
@@ -0,0 +1,284 @@
/**
* @module ol/source/IIIF
*/
import {DEFAULT_TILE_SIZE} from '../tilegrid/common.js';
import {getTopLeft} from '../extent.js';
import {CustomTile} from './Zoomify.js';
import {Versions} from '../format/IIIFInfo.js';
import {assert} from '../asserts.js';
import TileGrid from '../tilegrid/TileGrid.js';
import TileImage from './TileImage.js';
/**
* @typedef {Object} Options
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
* @property {number} [cacheSize]
* @property {null|string} [crossOrigin]
* @property {import("../extent.js").Extent} [extent=[0, -height, width, 0]]
* @property {string} [format='jpg'] Requested image format.
* @property {import("../proj.js").ProjectionLike} [projection]
* @property {string} [quality] Requested IIIF image quality. Default is 'native'
* for version 1, 'default' for versions 2 and 3.
* @property {number} [reprojectionErrorThreshold=0.5] Maximum allowed reprojection error (in pixels).
* Higher values can increase reprojection performance, but decrease precision.
* @property {import("../size.js").Size} size Size of the image [width, height].
* @property {import("../size.js").Size[]} [sizes] Supported scaled image sizes.
* Content of the IIIF info.json 'sizes' property, but as array of Size objects.
* @property {import("./State.js").default} [state] Source state.
* @property {Array<string>} [supports=[]] Supported IIIF region and size calculation
* features.
* @property {number} [tilePixelRatio]
* @property {number|import("../size.js").Size} [tileSize] Tile size.
* Same tile size is used for all zoom levels. If tile size is a number,
* a square tile is assumed. If the IIIF image service supports arbitrary
* tiling (sizeByH, sizeByW, sizeByWh or sizeByPct as well as regionByPx or regionByPct
* are supported), the default tilesize is 256.
* @property {number} [transition]
* @property {string} [url] Base URL of the IIIF Image service.
* This should be the same as the IIIF Image @id.
* @property {Versions} [version=Versions.VERSION2] Service's IIIF Image API version.
* @property {number} [zDirection] Indicate which resolution should be used
* by a renderer if the views resolution does not match any resolution of the tile source.
* If 0, the nearest resolution will be used. If 1, the nearest lower resolution
* will be used. If -1, the nearest higher resolution will be used.
*/
function formatPercentage(percentage) {
return percentage.toLocaleString('en', {maximumFractionDigits: 10});
}
/**
* @classdesc
* Layer source for IIIF Image API services.
* @api
*/
class IIIF extends TileImage {
constructor(opt_options) {
const options = opt_options || {};
let baseUrl = options.url || '';
baseUrl = baseUrl + (baseUrl.lastIndexOf('/') === baseUrl.length - 1 || baseUrl === '' ? '' : '/');
const version = options.version || Versions.VERSION2;
const sizes = options.sizes || [];
const size = options.size;
assert(size != undefined && Array.isArray(size) && size.length == 2 &&
!isNaN(size[0]) && size[0] > 0 && !isNaN(size[1]) && size[1] > 0, 60);
const width = size[0];
const height = size[1];
const tileSize = options.tileSize;
const format = options.format || 'jpg';
const quality = options.quality || (options.version == Versions.VERSION1 ? 'native' : 'default');
let resolutions = options.resolutions || [];
const supports = options.supports || [];
const extent = options.extent || [0, -height, width, 0];
const supportsListedSizes = sizes != undefined && Array.isArray(sizes) && sizes.length > 0;
const supportsListedTiles = tileSize != undefined && (Number.isInteger(tileSize) && tileSize > 0 || Array.isArray(tileSize) && tileSize.length > 0);
const supportsArbitraryTiling = supports != undefined && Array.isArray(supports) &&
(supports.includes('regionByPx') || supports.includes('regionByPct')) &&
(supports.includes('sizeByWh') || supports.includes('sizeByH') ||
supports.includes('sizeByW') || supports.includes('sizeByPct'));
let tileWidth,
tileHeight,
maxZoom;
resolutions.sort(function(a, b) {
return b - a;
});
if (supportsListedTiles || supportsArbitraryTiling) {
if (tileSize != undefined) {
if (Number.isInteger(tileSize) && tileSize > 0) {
tileWidth = tileSize;
tileHeight = tileSize;
} else if (Array.isArray(tileSize) && tileSize.length > 0) {
if (tileSize.length == 1 || tileSize[1] == undefined && Number.isInteger(tileSize[0])) {
tileWidth = tileSize[0];
tileHeight = tileSize[0];
}
if (tileSize.length == 2) {
if (Number.isInteger(tileSize[0]) && Number.isInteger(tileSize[1])) {
tileWidth = tileSize[0];
tileHeight = tileSize[1];
} else if (tileSize[0] == undefined && Number.isInteger(tileSize[1])) {
tileWidth = tileSize[1];
tileHeight = tileSize[1];
}
}
}
}
if (tileWidth === undefined || tileHeight === undefined) {
tileWidth = DEFAULT_TILE_SIZE;
tileHeight = DEFAULT_TILE_SIZE;
}
if (resolutions.length == 0) {
maxZoom = Math.max(
Math.ceil(Math.log(width / tileWidth) / Math.LN2),
Math.ceil(Math.log(height / tileHeight) / Math.LN2)
);
for (let i = maxZoom; i >= 0; i--) {
resolutions.push(Math.pow(2, i));
}
} else {
const maxScaleFactor = Math.max([...resolutions]);
// TODO maxScaleFactor might not be a power to 2
maxZoom = Math.round(Math.log(maxScaleFactor) / Math.LN2);
}
} else {
// No tile support.
tileWidth = width;
tileHeight = height;
resolutions = [];
if (supportsListedSizes) {
/*
* 'sizes' provided. Use full region in different resolutions. Every
* resolution has only one tile.
*/
sizes.sort(function(a, b) {
return a[0] - b[0];
});
maxZoom = -1;
const ignoredSizesIndex = [];
for (let i = 0; i < sizes.length; i++) {
const resolution = width / sizes[i][0];
if (resolutions.length > 0 && resolutions[resolutions.length - 1] == resolution) {
ignoredSizesIndex.push(i);
continue;
}
resolutions.push(resolution);
maxZoom++;
}
if (ignoredSizesIndex.length > 0) {
for (let i = 0; i < ignoredSizesIndex.length; i++) {
sizes.splice(ignoredSizesIndex[i] - i, 1);
}
}
} else {
// No useful image information at all. Try pseudo tile with full image.
resolutions.push(1);
sizes.push([width, height]);
maxZoom = 0;
}
}
const tileGrid = new TileGrid({
tileSize: [tileWidth, tileHeight],
extent: extent,
origin: getTopLeft(extent),
resolutions: resolutions
});
const tileUrlFunction = function(tileCoord, pixelRatio, projection) {
let regionParam,
sizeParam;
const zoom = tileCoord[0];
if (zoom > maxZoom) {
return;
}
const tileX = tileCoord[1],
tileY = tileCoord[2],
scale = resolutions[zoom];
if (tileX === undefined || tileY === undefined || scale === undefined ||
tileX < 0 || Math.ceil(width / scale / tileWidth) <= tileX ||
tileY < 0 || Math.ceil(height / scale / tileHeight) <= tileY) {
return;
}
if (supportsArbitraryTiling || supportsListedTiles) {
const regionX = tileX * tileWidth * scale,
regionY = tileY * tileHeight * scale;
let regionW = tileWidth * scale,
regionH = tileHeight * scale,
sizeW = tileWidth,
sizeH = tileHeight;
if (regionX + regionW > width) {
regionW = width - regionX;
}
if (regionY + regionH > height) {
regionH = height - regionY;
}
if (regionX + tileWidth * scale > width) {
sizeW = Math.floor((width - regionX + scale - 1) / scale);
}
if (regionY + tileHeight * scale > height) {
sizeH = Math.floor((height - regionY + scale - 1) / scale);
}
if (regionX == 0 && regionW == width && regionY == 0 && regionH == height) {
// canonical full image region parameter is 'full', not 'x,y,w,h'
regionParam = 'full';
} else if (!supportsArbitraryTiling || supports.includes('regionByPx')) {
regionParam = regionX + ',' + regionY + ',' + regionW + ',' + regionH;
} else if (supports.includes('regionByPct')) {
const pctX = formatPercentage(regionX / width * 100),
pctY = formatPercentage(regionY / height * 100),
pctW = formatPercentage(regionW / width * 100),
pctH = formatPercentage(regionH / height * 100);
regionParam = 'pct:' + pctX + ',' + pctY + ',' + pctW + ',' + pctH;
}
if (version == Versions.VERSION3 && (!supportsArbitraryTiling || supports.includes('sizeByWh'))) {
sizeParam = sizeW + ',' + sizeH;
} else if (!supportsArbitraryTiling || supports.includes('sizeByW')) {
sizeParam = sizeW + ',';
} else if (supports.includes('sizeByH')) {
sizeParam = ',' + sizeH;
} else if (supports.includes('sizeByWh')) {
sizeParam = sizeW + ',' + sizeH;
} else if (supports.includes('sizeByPct')) {
sizeParam = 'pct:' + formatPercentage(100 / scale);
}
} else {
regionParam = 'full';
if (supportsListedSizes) {
const regionWidth = sizes[zoom][0],
regionHeight = sizes[zoom][1];
if (version == Versions.VERSION3) {
if (regionWidth == width && regionHeight == height) {
sizeParam = 'max';
} else {
sizeParam = regionWidth + ',' + regionHeight;
}
} else {
if (regionWidth == width) {
sizeParam = 'full';
} else {
sizeParam = regionWidth + ',';
}
}
} else {
sizeParam = version == Versions.VERSION3 ? 'max' : 'full';
}
}
return baseUrl + regionParam + '/' + sizeParam + '/0/' + quality + '.' + format;
};
const IiifTileClass = CustomTile.bind(null, tileGrid);
super({
attributions: options.attributions,
attributionsCollapsible: options.attributionsCollapsible,
cacheSize: options.cacheSize,
crossOrigin: options.crossOrigin,
projection: options.projection,
reprojectionErrorThreshold: options.reprojectionErrorThreshold,
state: options.state,
tileClass: IiifTileClass,
tileGrid: tileGrid,
tilePixelRatio: options.tilePixelRatio,
tileUrlFunction: tileUrlFunction,
transition: options.transition
});
/**
* @inheritDoc
*/
this.zDirection = options.zDirection;
}
}
export default IIIF;
+1
View File
@@ -237,6 +237,7 @@ class TileGrid {
/** /**
* Get the extent for this tile grid, if it was configured. * Get the extent for this tile grid, if it was configured.
* @return {import("../extent.js").Extent} Extent. * @return {import("../extent.js").Extent} Extent.
* @api
*/ */
getExtent() { getExtent() {
return this.extent_; return this.extent_;
+487
View File
@@ -0,0 +1,487 @@
import IIIFInfo from '../../../../src/ol/format/IIIFInfo.js';
import {Versions} from '../../../../src/ol/format/IIIFInfo.js';
describe('ol.format.IIIFInfo', function() {
const iiifInfo = new IIIFInfo();
describe('setImageInfo', function() {
it('can handle image info JSON as object or as string serialization', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
'@id': 'http://iiif.test/id'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION3);
iiifInfo.setImageInfo('{"@context": "http://iiif.io/api/image/2/context.json","@id":"http://iiif.test/id"}');
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION2);
});
});
describe('getImageApiVersion', function() {
it('provides the correct Image API version', function() {
iiifInfo.setImageInfo({
'@id': 'http://iiif.test/id'
});
expect(function() {
iiifInfo.getImageApiVersion();
}).to.throwException();
iiifInfo.setImageInfo({
identifier: 'http://iiif.test/id',
profile: 'this is no valid profile'
});
expect(function() {
iiifInfo.getImageApiVersion();
}).to.throwException();
iiifInfo.setImageInfo({
'@context': 'this is no valid context',
'@id': 'http://iiif.test/id'
});
expect(function() {
iiifInfo.getImageApiVersion();
}).to.throwException();
iiifInfo.setImageInfo({
identifier: 'http://iiif.test/id',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION1);
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
'@id': 'http://iiif.test/id'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION1);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/1/context.json',
identifier: 'http://iiif.test/id'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION1);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION2);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
id: 'http://iiif.test/id'
});
expect(iiifInfo.getImageApiVersion()).to.be(Versions.VERSION3);
});
});
describe('getComplianceLevelFromProfile', function() {
it('detects the correct compliance level', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'level0'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'http://iiif.io/api/image/level3.json'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'level1'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'http://iiif.io/api/image/2/level2.json'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be('level2');
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: ['http://iiif.io/api/image/2/level1.json']
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be('level1');
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
profile: 'level4'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
profile: 'http://iiif.io/api/image/3/level3.json'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
profile: 'http://iiif.io/api/image/2/level1.json'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
profile: 'level2'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be('level2');
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
profile: 'http://iiif.io/api/image/3/level1.json'
});
expect(iiifInfo.getComplianceLevelFromProfile()).to.be('level1');
});
});
describe('getComplianceLevelSupportedFeatures', function() {
it('provides the correct features for given versions and compliance levels', function() {
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0'
});
let level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.be.empty();
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
});
level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.have.length(4);
expect(level.supports).to.contain('regionByPx');
expect(level.supports).to.contain('sizeByW');
expect(level.supports).to.contain('sizeByH');
expect(level.supports).to.contain('sizeByPct');
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level2'
});
level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.have.length(7);
expect(level.supports).to.contain('regionByPx');
expect(level.supports).to.contain('regionByPct');
expect(level.supports).to.contain('sizeByW');
expect(level.supports).to.contain('sizeByH');
expect(level.supports).to.contain('sizeByPct');
expect(level.supports).to.contain('sizeByConfinedWh');
expect(level.supports).to.contain('sizeByWh');
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'http://iiif.io/api/image/2/level0.json'
});
level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.be.empty();
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'http://iiif.io/api/image/2/level1.json'
});
level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.have.length(4);
expect(level.supports).to.contain('regionByPx');
expect(level.supports).to.contain('sizeByW');
expect(level.supports).to.contain('sizeByH');
expect(level.supports).to.contain('sizeByPct');
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
profile: 'http://iiif.io/api/image/2/level2.json'
});
level = iiifInfo.getComplianceLevelSupportedFeatures();
expect(level.supports).to.have.length(8);
expect(level.supports).to.contain('regionByPx');
expect(level.supports).to.contain('regionByPct');
expect(level.supports).to.contain('sizeByW');
expect(level.supports).to.contain('sizeByH');
expect(level.supports).to.contain('sizeByPct');
expect(level.supports).to.contain('sizeByConfinedWh');
expect(level.supports).to.contain('sizeByDistortedWh');
expect(level.supports).to.contain('sizeByWh');
// TODO test version 3 compliance level features once version 3 is final
});
});
describe('getTileSourceOptions', function() {
it('produces options from minimal information responses', function() {
expect(function() {
iiifInfo.setImageInfo({
width: 2000,
height: 1500
});
iiifInfo.getTileSourceOptions();
}).to.throwException();
iiifInfo.setImageInfo({
identifier: 'id',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0'
});
let options = iiifInfo.getTileSourceOptions();
expect(options).to.be.an('object');
expect(options).to.have.property('version', Versions.VERSION1);
iiifInfo.setImageInfo({
identifier: 'identifier-version-1.0',
width: 2000,
height: 1500,
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0'
});
options = iiifInfo.getTileSourceOptions();
expect(options).to.not.be(undefined);
expect(options).to.not.be(null);
expect(options).to.have.property('version', Versions.VERSION1);
expect(options).to.have.property('size');
expect(options.size).to.be.an('array');
expect(options.size.length).to.be(2);
expect(options.size[0]).to.be(2000);
expect(options.size[1]).to.be(1500);
expect(options.quality).to.be('native');
expect(options.url).to.be(undefined);
expect(options.sizes).to.be(undefined);
expect(options.tileSize).to.be(undefined);
expect(options.format).to.be('jpg');
expect(options.supports).to.be.empty();
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/version2/id'
});
options = iiifInfo.getTileSourceOptions();
expect(options).to.be.an('object');
expect(options).to.have.property('version', Versions.VERSION2);
expect(options).to.have.property('url', 'http://iiif.test/version2/id');
expect(options).to.have.property('format', 'jpg');
});
it('uses preferred options if applicable', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/version2/id',
width: 2000,
height: 1500,
profile: ['http://iiif.io/api/image/2/level2.json']
});
const options = iiifInfo.getTileSourceOptions({
quality: 'bitonal',
format: 'png'
});
expect(options).to.have.property('quality', 'bitonal');
expect(options).to.have.property('format', 'png');
});
it('ignores preferred options that are not supported', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/version2/id',
width: 2000,
height: 1500,
profile: ['http://iiif.io/api/image/2/level1.json']
});
const options = iiifInfo.getTileSourceOptions({
quality: 'bitonal',
format: 'png'
});
expect(options).to.have.property('quality', 'default');
expect(options).to.have.property('format', 'jpg');
});
it('combines supported features indicated by compliance level and explicitly stated in image info', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id',
profile: ['http://iiif.io/api/image/2/level1.json', {
supports: ['regionByPct', 'sizeByWh']
}]
});
let options = iiifInfo.getTileSourceOptions();
expect(options.supports).to.contain('regionByPct');
expect(options.supports).to.contain('sizeByWh');
expect(options.supports).to.contain('regionByPx');
expect(options.supports).to.contain('sizeByW');
expect(options.supports).to.contain('sizeByH');
expect(options.supports).to.contain('sizeByPct');
expect(options.supports).to.have.length(6);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/3/context.json',
id: 'http://iiif.test/id',
profile: 'level1',
extraFeatures: ['regionByPct', 'sizeByPct']
});
options = iiifInfo.getTileSourceOptions();
expect(options.supports).to.contain('regionByPct');
expect(options.supports).to.contain('sizeByPct');
expect(options.supports).to.contain('regionByPx');
expect(options.supports).to.contain('regionSquare');
expect(options.supports).to.contain('sizeByW');
expect(options.supports).to.contain('sizeByH');
expect(options.supports).to.have.length(6);
});
it('uses the first available scale factors and tile sizes', function() {
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0'
});
let options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.be(undefined);
expect(options.tileSize).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0',
scale_factors: [1, 2, 4],
tile_width: 512
});
options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.have.length(3);
expect(options.resolutions).to.contain(1);
expect(options.resolutions).to.contain(2);
expect(options.resolutions).to.contain(4);
expect(options.tileSize).to.have.length(2);
expect(options.tileSize[0]).to.be(512);
expect(options.tileSize[1]).to.be(512);
iiifInfo.setImageInfo({
'@context': 'http://library.stanford.edu/iiif/image-api/1.1/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://library.stanford.edu/iiif/image-api/compliance.html#level0',
scale_factors: [1, 2, 4],
tile_width: 512,
tile_height: 1024
});
options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.have.length(3);
expect(options.resolutions).to.contain(1);
expect(options.resolutions).to.contain(2);
expect(options.resolutions).to.contain(4);
expect(options.tileSize).to.have.length(2);
expect(options.tileSize[0]).to.be(512);
expect(options.tileSize[1]).to.be(1024);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://iiif.io/api/image/2/level0.json'
});
options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.be(undefined);
expect(options.tileSize).to.be(undefined);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://iiif.io/api/image/2/level0.json',
tiles: [{
scaleFactors: [1, 2, 4],
width: 512
},
{
scaleFactors: [1, 2, 4, 8, 16],
width: 256
}]
});
options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.have.length(3);
expect(options.resolutions).to.contain(1);
expect(options.resolutions).to.contain(2);
expect(options.resolutions).to.contain(4);
expect(options.tileSize).to.have.length(2);
expect(options.tileSize[0]).to.be(512);
expect(options.tileSize[1]).to.be(512);
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id',
profile: 'http://iiif.io/api/image/2/level0.json',
tiles: [{
scaleFactors: [1, 2, 4],
width: 512,
height: 1024
}]
});
options = iiifInfo.getTileSourceOptions();
expect(options.resolutions).to.have.length(3);
expect(options.resolutions).to.contain(1);
expect(options.resolutions).to.contain(2);
expect(options.resolutions).to.contain(4);
expect(options.tileSize).to.have.length(2);
expect(options.tileSize[0]).to.be(512);
expect(options.tileSize[1]).to.be(1024);
});
});
it('provides each given size in sizes as OpenLayers Size', function() {
iiifInfo.setImageInfo({
'@context': 'http://iiif.io/api/image/2/context.json',
'@id': 'http://iiif.test/id',
'sizes': [{
width: 2000,
height: 1000
},
{
width: 1000,
height: 500
},
{
width: 500,
height: 250
}]
});
const options = iiifInfo.getTileSourceOptions();
expect(options.sizes).to.have.length(3);
expect(options.sizes[0]).to.have.length(2);
expect(options.sizes[0][0]).to.be(2000);
expect(options.sizes[0][1]).to.be(1000);
expect(options.sizes[1]).to.have.length(2);
expect(options.sizes[1][0]).to.be(1000);
expect(options.sizes[1][1]).to.be(500);
expect(options.sizes[2]).to.have.length(2);
expect(options.sizes[2][0]).to.be(500);
expect(options.sizes[2][1]).to.be(250);
});
});
+387
View File
@@ -0,0 +1,387 @@
import {DEFAULT_TILE_SIZE} from '../../../../src/ol/tilegrid/common.js';
import IIIF from '../../../../src/ol/source/IIIF.js';
import {Versions} from '../../../../src/ol/format/IIIFInfo.js';
describe('ol.source.IIIF', function() {
const width = 2000,
height = 1500,
size = [width, height],
url = 'http://iiif.test/image-id';
function getMinimalSource() {
return new IIIF({
size: size
});
}
function getSource(additionalOptions) {
const options = Object.assign({}, {
size: size,
url: url
}, additionalOptions === undefined ? {} : additionalOptions);
return new IIIF(options);
}
describe('constructor', function() {
it('requires valid size option', function() {
expect(function() {
new IIIF();
}).to.throwException();
expect(function() {
new IIIF({});
}).to.throwException();
expect(function() {
new IIIF({
size: []
});
}).to.throwException();
expect(function() {
new IIIF({
size: 100
});
}).to.throwException();
expect(function() {
new IIIF({
size: [100]
});
}).to.throwException();
expect(function() {
new IIIF({
size: [null, 100]
});
}).to.throwException();
expect(function() {
new IIIF({
size: ['very wide', 100]
});
}).to.throwException();
expect(function() {
new IIIF({
size: [0, 100]
});
}).to.throwException();
expect(function() {
new IIIF({
size: [100, null]
});
}).to.throwException();
expect(function() {
new IIIF({
size: [100, 0]
});
}).to.throwException();
expect(function() {
new IIIF({
size: [100, 'not that high']
});
}).to.throwException();
expect(function() {
new IIIF({
size: [100, 200, 300]
});
}).to.throwException();
let source;
expect(function() {
source = new IIIF({
size: [100, 200]
});
}).to.not.throwException();
expect(source).to.be.a(IIIF);
expect(function() {
getMinimalSource();
}).to.not.throwException();
});
it('uses empty base URL, default quality, jpg format as default', function() {
const tileUrlFunction = getMinimalSource().getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('full/full/0/default.jpg');
});
it('uses native as default quality for version 1', function() {
const tileUrlFunction = new IIIF({
size: size,
version: Versions.VERSION1
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('full/full/0/native.jpg');
});
it('corrects non empty base URL if trailing slash is missing', function() {
// missing trailing slash is added
let tileUrlFunction = getSource().getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/default.jpg');
// existent trailing slash isn't doubled
tileUrlFunction = getSource({
url: 'http://iiif.test/other-image-id/'
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/other-image-id/full/full/0/default.jpg');
});
});
describe('tileUrlFunction', function() {
it('has only one resolution and one tile if no tiles, resolutions, sizes and supported features are given', function() {
let tileUrlFunction = getSource().getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/default.jpg');
expect(tileUrlFunction([-1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([0, 1, 0])).to.be(undefined);
expect(tileUrlFunction([0, 0, 1])).to.be(undefined);
tileUrlFunction = getSource({
version: Versions.VERSION1
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/native.jpg');
tileUrlFunction = getSource({
version: Versions.VERSION3
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/max/0/default.jpg');
});
it('constructs the same number of resolutions as distinguishable sizes are given', function() {
let tileUrlFunction = getSource({
sizes: [[2000, 1500], [1000, 750], [500, 375]]
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/full/1000,/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be(undefined);
expect(tileUrlFunction([-1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([0, 1, 0])).to.be(undefined);
expect(tileUrlFunction([0, 0, 1])).to.be(undefined);
expect(tileUrlFunction([1, 1, 0])).to.be(undefined);
expect(tileUrlFunction([1, 0, 1])).to.be(undefined);
tileUrlFunction = getSource({
sizes: [[2000, 1500], [1000, 750], [500, 375]],
version: Versions.VERSION3
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,375/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/full/1000,750/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/full/max/0/default.jpg');
tileUrlFunction = getSource({
sizes: [[2000, 1500], [1000, 749], [1000, 750], [500, 375], [500, 374]]
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/full/1000,/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be(undefined);
});
it('cannot provide scaled tiles without provided tilesize or supported features', function() {
const tileUrlFunction = getSource({
resolutions: [16, 8, 4, 2, 1]
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/full/0/default.jpg');
expect(tileUrlFunction([-1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([0, 1, 0])).to.be(undefined);
expect(tileUrlFunction([0, 0, 1])).to.be(undefined);
});
it('provides canonical tile URLs for all necessary resolutions if only a tileSize exists', function() {
let tileUrlFunction = getSource({
tileSize: 512
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,/0/default.jpg');
expect(tileUrlFunction([-1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([0, 1, 0])).to.be(undefined);
expect(tileUrlFunction([0, 0, 1])).to.be(undefined);
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/0,0,1024,1024/512,/0/default.jpg');
expect(tileUrlFunction([1, 1, 0])).to.be('http://iiif.test/image-id/1024,0,976,1024/488,/0/default.jpg');
expect(tileUrlFunction([1, 0, 1])).to.be('http://iiif.test/image-id/0,1024,1024,476/512,/0/default.jpg');
expect(tileUrlFunction([1, 1, 1])).to.be('http://iiif.test/image-id/1024,1024,976,476/488,/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,512,512/512,/0/default.jpg');
expect(tileUrlFunction([2, 3, 0])).to.be('http://iiif.test/image-id/1536,0,464,512/464,/0/default.jpg');
expect(tileUrlFunction([2, 0, 2])).to.be('http://iiif.test/image-id/0,1024,512,476/512,/0/default.jpg');
expect(tileUrlFunction([2, 3, 2])).to.be('http://iiif.test/image-id/1536,1024,464,476/464,/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be(undefined);
tileUrlFunction = getSource({
tileSize: 512,
version: Versions.VERSION3
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,375/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/0,0,1024,1024/512,512/0/default.jpg');
expect(tileUrlFunction([1, 1, 0])).to.be('http://iiif.test/image-id/1024,0,976,1024/488,512/0/default.jpg');
expect(tileUrlFunction([1, 0, 1])).to.be('http://iiif.test/image-id/0,1024,1024,476/512,238/0/default.jpg');
expect(tileUrlFunction([1, 1, 1])).to.be('http://iiif.test/image-id/1024,1024,976,476/488,238/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,512,512/512,512/0/default.jpg');
expect(tileUrlFunction([2, 3, 0])).to.be('http://iiif.test/image-id/1536,0,464,512/464,512/0/default.jpg');
expect(tileUrlFunction([2, 0, 2])).to.be('http://iiif.test/image-id/0,1024,512,476/512,476/0/default.jpg');
expect(tileUrlFunction([2, 3, 2])).to.be('http://iiif.test/image-id/1536,1024,464,476/464,476/0/default.jpg');
});
it('provides canonical tile URLs for all provided resolutions if a tileSize also exists', function() {
const tileUrlFunction = getSource({
tileSize: 512,
resolutions: [8, 4, 2, 1]
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/250,/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/full/500,/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,1024,1024/512,/0/default.jpg');
expect(tileUrlFunction([2, 1, 0])).to.be('http://iiif.test/image-id/1024,0,976,1024/488,/0/default.jpg');
expect(tileUrlFunction([2, 0, 1])).to.be('http://iiif.test/image-id/0,1024,1024,476/512,/0/default.jpg');
expect(tileUrlFunction([2, 1, 1])).to.be('http://iiif.test/image-id/1024,1024,976,476/488,/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be('http://iiif.test/image-id/0,0,512,512/512,/0/default.jpg');
expect(tileUrlFunction([3, 3, 0])).to.be('http://iiif.test/image-id/1536,0,464,512/464,/0/default.jpg');
expect(tileUrlFunction([3, 0, 2])).to.be('http://iiif.test/image-id/0,1024,512,476/512,/0/default.jpg');
expect(tileUrlFunction([3, 3, 2])).to.be('http://iiif.test/image-id/1536,1024,464,476/464,/0/default.jpg');
expect(tileUrlFunction([4, 0, 0])).to.be(undefined);
});
it('supports non square tiles', function() {
let tileUrlFunction = getSource({
tileSize: [1024, 512]
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,/0/default.jpg');
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/0,0,2000,1024/1000,/0/default.jpg');
expect(tileUrlFunction([1, 0, 1])).to.be('http://iiif.test/image-id/0,1024,2000,476/1000,/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,1024,512/1024,/0/default.jpg');
expect(tileUrlFunction([2, 1, 0])).to.be('http://iiif.test/image-id/1024,0,976,512/976,/0/default.jpg');
expect(tileUrlFunction([2, 0, 2])).to.be('http://iiif.test/image-id/0,1024,1024,476/1024,/0/default.jpg');
expect(tileUrlFunction([2, 1, 2])).to.be('http://iiif.test/image-id/1024,1024,976,476/976,/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be(undefined);
tileUrlFunction = getSource({
tileSize: [1024, 512],
version: Versions.VERSION3
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/500,375/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,1024,512/1024,512/0/default.jpg');
});
it('provides tile URLs with default tile size if sufficient supported features are provided', function() {
let tileUrlFunction = getSource({
supports: ['regionByPx', 'sizeByW']
}).getTileUrlFunction();
const maxZoom = Math.ceil(Math.log2(width / DEFAULT_TILE_SIZE));
expect(tileUrlFunction([maxZoom, 0, 0])).to.be('http://iiif.test/image-id/0,0,' + DEFAULT_TILE_SIZE + ',' + DEFAULT_TILE_SIZE + '/' + DEFAULT_TILE_SIZE + ',/0/default.jpg');
expect(tileUrlFunction([maxZoom + 1, 0, 0])).to.be(undefined);
tileUrlFunction = getSource({
supports: ['regionByPx', 'sizeByH']
}).getTileUrlFunction();
expect(tileUrlFunction([maxZoom, 0, 0])).to.be('http://iiif.test/image-id/0,0,' + DEFAULT_TILE_SIZE + ',' + DEFAULT_TILE_SIZE + '/,' + DEFAULT_TILE_SIZE + '/0/default.jpg');
expect(tileUrlFunction([maxZoom + 1, 0, 0])).to.be(undefined);
tileUrlFunction = getSource({
supports: ['regionByPx', 'sizeByWh']
}).getTileUrlFunction();
expect(tileUrlFunction([maxZoom, 0, 0])).to.be('http://iiif.test/image-id/0,0,' + DEFAULT_TILE_SIZE + ',' + DEFAULT_TILE_SIZE + '/' + DEFAULT_TILE_SIZE + ',' + DEFAULT_TILE_SIZE + '/0/default.jpg');
expect(tileUrlFunction([maxZoom + 1, 0, 0])).to.be(undefined);
tileUrlFunction = getSource({
supports: ['regionByPct', 'sizeByPct']
}).getTileUrlFunction();
const tileWPct = (DEFAULT_TILE_SIZE / width * 100).toLocaleString('en', {maximumFractionDigits: 10}),
tileHPct = (DEFAULT_TILE_SIZE / height * 100).toLocaleString('en', {maximumFractionDigits: 10});
expect(tileUrlFunction([maxZoom, 0, 0])).to.be('http://iiif.test/image-id/pct:0,0,' + tileWPct + ',' + tileHPct + '/pct:100/0/default.jpg');
expect(tileUrlFunction([maxZoom + 1, 0, 0])).to.be(undefined);
});
it('prefers canonical tile URLs', function() {
let tileUrlFunction = getSource({
tileSize: 512,
supports: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByWh', 'sizeByPct']
}).getTileUrlFunction();
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,512,512/512,/0/default.jpg');
tileUrlFunction = getSource({
tileSize: 512,
version: Versions.VERSION3,
supports: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByWh', 'sizeByPct']
}).getTileUrlFunction();
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/0,0,512,512/512,512/0/default.jpg');
});
it('provides correct tile URLs for percentage URL parameter values', function() {
const tileUrlFunction = getSource({
tileSize: 512,
supports: ['regionByPct', 'sizeByPct']
}).getTileUrlFunction();
expect(tileUrlFunction([0, 0, 0])).to.be('http://iiif.test/image-id/full/pct:25/0/default.jpg');
expect(tileUrlFunction([-1, 0, 0])).to.be(undefined);
expect(tileUrlFunction([0, 1, 0])).to.be(undefined);
expect(tileUrlFunction([0, 0, 1])).to.be(undefined);
expect(tileUrlFunction([1, 0, 0])).to.be('http://iiif.test/image-id/pct:0,0,51.2,68.2666666667/pct:50/0/default.jpg');
expect(tileUrlFunction([1, 1, 0])).to.be('http://iiif.test/image-id/pct:51.2,0,48.8,68.2666666667/pct:50/0/default.jpg');
expect(tileUrlFunction([1, 0, 1])).to.be('http://iiif.test/image-id/pct:0,68.2666666667,51.2,31.7333333333/pct:50/0/default.jpg');
expect(tileUrlFunction([1, 1, 1])).to.be('http://iiif.test/image-id/pct:51.2,68.2666666667,48.8,31.7333333333/pct:50/0/default.jpg');
expect(tileUrlFunction([2, 0, 0])).to.be('http://iiif.test/image-id/pct:0,0,25.6,34.1333333333/pct:100/0/default.jpg');
expect(tileUrlFunction([2, 3, 0])).to.be('http://iiif.test/image-id/pct:76.8,0,23.2,34.1333333333/pct:100/0/default.jpg');
expect(tileUrlFunction([2, 0, 2])).to.be('http://iiif.test/image-id/pct:0,68.2666666667,25.6,31.7333333333/pct:100/0/default.jpg');
expect(tileUrlFunction([2, 3, 2])).to.be('http://iiif.test/image-id/pct:76.8,68.2666666667,23.2,31.7333333333/pct:100/0/default.jpg');
expect(tileUrlFunction([3, 0, 0])).to.be(undefined);
});
});
});