Add IIIF Image API tilesource with example
src/ol/source/IIIF.js contains a tile source for IIIF Image API services. It supports Image API version 1 and 2 on compliance levels 0, 1 and 2. To get working constructor options for IIIF from an IIIF image info.json, use src/ol/format/IIIFInfo.js. An example is available in examples/iiif.html respectivly examples/iiif.js.
This commit is contained in:
245
src/ol/source/IIIF.js
Normal file
245
src/ol/source/IIIF.js
Normal file
@@ -0,0 +1,245 @@
|
||||
/**
|
||||
* @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("../proj.js").ProjectionLike} [projection]
|
||||
* @property {number} [tilePixelRatio]
|
||||
* @property {number} [reprojectionErrorThreshold=0.5]
|
||||
* @property {string} [url] Base URL of the IIIF Image service.
|
||||
* This shoulf be the same as the IIIF Image @id.
|
||||
* @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("../extent.js").Extent} [extent=[0, -height, width, 0]]
|
||||
* @property {number} [transition]
|
||||
* @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 or sizeByPct as well as regionByPx and regionByPct
|
||||
* are supported), the default tilesize is 256.
|
||||
* @property {boolean} [wrapX=false]
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Layer source for tile data in IIIF format.
|
||||
* @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;
|
||||
// TODO Appropriate error code
|
||||
assert(size != undefined && Array.isArray(size) && size.length == 2 &&
|
||||
!isNaN(size[0]) && size[0] > 0 && !isNaN(size[1] && size[1] > 0), 999);
|
||||
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 features = options.features || [];
|
||||
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 = features != undefined && Array.isArray(features) &&
|
||||
(features.includes('regionByPx') || features.includes('regionByPct')) &&
|
||||
(features.includes('sizeByWh') || features.includes('sizeByH') ||
|
||||
features.includes('sizeByW') || features.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];
|
||||
});
|
||||
for (let i = 0; i < sizes.length; i++) {
|
||||
const resolution = width / sizes[i][0];
|
||||
resolutions.push(resolution);
|
||||
maxZoom = i;
|
||||
}
|
||||
} 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 (maxZoom < zoom) {
|
||||
return;
|
||||
}
|
||||
const tileX = tileCoord[1],
|
||||
tileY = tileCoord[2],
|
||||
scale = resolutions[zoom];
|
||||
if (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);
|
||||
}
|
||||
const sizeHBySizeW = Math.round(sizeW / regionW * regionH),
|
||||
sizeWBySizeH = Math.round(sizeH / regionH * regionW),
|
||||
preferSizeByH = (sizeHBySizeW > sizeH) && (sizeW == sizeWBySizeH);
|
||||
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 || features.includes('regionByPx')) {
|
||||
regionParam = regionX + ',' + regionY + ',' + regionW + ',' + regionH;
|
||||
} else if (features.includes('regionByPct')) {
|
||||
const pctX = regionX / width * 100,
|
||||
pctY = regionY / height * 100,
|
||||
pctW = regionW / width * 100,
|
||||
pctH = regionH / height * 100;
|
||||
regionParam = 'pct:' + pctX + ',' + pctY + ',' + pctW + ',' + pctH;
|
||||
}
|
||||
if (version == Versions.VERSION3 && (!supportsArbitraryTiling || features.includes('sizeByWh'))) {
|
||||
sizeParam = sizeW + ',' + sizeH;
|
||||
} else if (!supportsArbitraryTiling || features.includes('sizeByW') && (!preferSizeByH || !(features.includes('sizeByH')))) {
|
||||
sizeParam = sizeW + ',';
|
||||
} else if (features.includes('sizeByH')) {
|
||||
sizeParam = ',' + sizeH;
|
||||
} else if (features.includes('sizeByWh')) {
|
||||
sizeParam = sizeW + ',' + sizeH;
|
||||
} else if (features.includes('sizeByPct')) {
|
||||
sizeParam = 'pct:' + (100 / scale);
|
||||
}
|
||||
} else {
|
||||
regionParam = 'full';
|
||||
if (supportsListedSizes) {
|
||||
sizeParam = sizes[zoom][0] + ',' + (version == Versions.VERSION3 ? sizes[zoom][0] : '');
|
||||
} 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,
|
||||
opaque: options.opaque,
|
||||
projection: options.projection,
|
||||
reprojectionErrorThreshold: options.reprojectionErrorThreshold,
|
||||
state: options.state,
|
||||
tileClass: IiifTileClass,
|
||||
transition: options.transition,
|
||||
wrapX: options.wrapX !== undefined ? options.wrapX : false,
|
||||
tileGrid: tileGrid,
|
||||
tilePixelRatio: options.tilePixelRatio,
|
||||
tileUrlFunction: tileUrlFunction
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default IIIF;
|
||||
Reference in New Issue
Block a user