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:
16
examples/iiif.html
Normal file
16
examples/iiif.html
Normal 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"> </div>
|
||||||
|
Enter <code>info.json</code> URL:
|
||||||
|
<input type="text" id="imageInfoUrl" value="https://iiif.ub.uni-leipzig.de/iiif/j2k/0000/0102/0000010218/00000061.jpx/info.json">
|
||||||
|
<button id="display">Display image</button>
|
||||||
|
</div>
|
||||||
48
examples/iiif.js
Normal file
48
examples/iiif.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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'
|
||||||
|
});
|
||||||
|
|
||||||
|
const notifyDiv = document.getElementById('iiif-notification');
|
||||||
|
|
||||||
|
function refreshMap(imageInfoUrl) {
|
||||||
|
fetch(imageInfoUrl).then(function(response) {
|
||||||
|
response.json().then(function(imageInfo) {
|
||||||
|
const options = new IIIFInfo().readFromJson(imageInfo);
|
||||||
|
if (options === undefined || options.version === undefined) {
|
||||||
|
notifyDiv.textContent = 'Data seems to be no valid IIIF image information.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const extent = [0, -options.size[1], options.size[0], 0];
|
||||||
|
layer.setSource(new IIIF(options));
|
||||||
|
map.setView(new View({
|
||||||
|
resolutions: layer.getSource().getTileGrid().getResolutions(),
|
||||||
|
extent: extent,
|
||||||
|
constrainOnlyCenter: true
|
||||||
|
}));
|
||||||
|
map.getView().fit(extent);
|
||||||
|
notifyDiv.textContent = '';
|
||||||
|
}).catch(function(body) {
|
||||||
|
notifyDiv.textContent = 'Could not read image info json. ' + body;
|
||||||
|
});
|
||||||
|
}).catch(function() {
|
||||||
|
notifyDiv.textContent = 'Could not read data from URL.';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const urlInput = document.getElementById('imageInfoUrl');
|
||||||
|
const displayButton = document.getElementById('display');
|
||||||
|
|
||||||
|
displayButton.addEventListener('click', function() {
|
||||||
|
const imageInfoUrl = urlInput.value;
|
||||||
|
refreshMap(imageInfoUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
refreshMap(urlInput.value);
|
||||||
@@ -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';
|
||||||
|
|||||||
266
src/ol/format/IIIFInfo.js
Normal file
266
src/ol/format/IIIFInfo.js
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
/**
|
||||||
|
* @module ol/format/IIIFInfo
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supported image formats, qualities and region / size calculation features
|
||||||
|
* for different image API versions and compliance levels
|
||||||
|
* @const
|
||||||
|
* @type {Object<string, Object<string, Array<string>>>}
|
||||||
|
*/
|
||||||
|
const IIIF_PROFILE_VALUES = {
|
||||||
|
version1: {
|
||||||
|
level0: {
|
||||||
|
features: [],
|
||||||
|
formats: [],
|
||||||
|
qualities: ['native']
|
||||||
|
},
|
||||||
|
level1: {
|
||||||
|
features: ['regionByPx', 'sizeByW', 'sizeByH', 'sizeByPct'],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['native']
|
||||||
|
},
|
||||||
|
level2: {
|
||||||
|
features: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByPct',
|
||||||
|
'sizeByConfinedWh', 'sizeByWh'],
|
||||||
|
formats: ['jpg', 'png'],
|
||||||
|
qualities: ['native', 'color', 'grey', 'bitonal']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
version2: {
|
||||||
|
level0: {
|
||||||
|
features: [],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['default']
|
||||||
|
},
|
||||||
|
level1: {
|
||||||
|
features: ['regionByPx', 'sizeByW', 'sizeByH', 'sizeByPct'],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['default']
|
||||||
|
},
|
||||||
|
level2: {
|
||||||
|
features: ['regionByPx', 'regionByPct', 'sizeByW', 'sizeByH', 'sizeByPct',
|
||||||
|
'sizeByConfinedWh', 'sizeByDistortedWh', 'sizeByWh'],
|
||||||
|
formats: ['jpg', 'png'],
|
||||||
|
qualities: ['default', 'bitonal']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
version3: {
|
||||||
|
level0: {
|
||||||
|
features: [],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['default']
|
||||||
|
},
|
||||||
|
level1: {
|
||||||
|
features: ['regionByPx', 'regionSquare', 'sizeByW', 'sizeByH'],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['default']
|
||||||
|
},
|
||||||
|
level2: {
|
||||||
|
features: ['regionByPx', 'regionSquare', 'regionByPct',
|
||||||
|
'sizeByW', 'sizeByH', 'sizeByPct', 'sizeByConfinedWh', 'sizeByWh'],
|
||||||
|
formats: ['jpg'],
|
||||||
|
qualities: ['default', 'bitonal']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
none: {
|
||||||
|
features: [],
|
||||||
|
formats: [],
|
||||||
|
qualities: []
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Versions = {
|
||||||
|
VERSION1: 'version1',
|
||||||
|
VERSION2: 'version2',
|
||||||
|
VERSION3: 'version3'
|
||||||
|
};
|
||||||
|
|
||||||
|
function getComplianceLevelOfImageInfoForVersion(imageInfo, version) {
|
||||||
|
switch (version) {
|
||||||
|
case Versions.VERSION1:
|
||||||
|
case Versions.VERSION3:
|
||||||
|
return imageInfo.profile;
|
||||||
|
case Versions.VERSION2:
|
||||||
|
if (typeof imageInfo.profile === 'string') {
|
||||||
|
return imageInfo.profile;
|
||||||
|
}
|
||||||
|
if (Array.isArray(imageInfo.profile) && imageInfo.profile.length > 0
|
||||||
|
&& typeof imageInfo.profile[0] === 'string') {
|
||||||
|
return imageInfo.profile[0];
|
||||||
|
}
|
||||||
|
// TODO error: cannot get compliance level URL / string
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// TODO error: invalid Image API version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVersionOfImageInfo(imageInfo) {
|
||||||
|
const context = imageInfo['@context'] || undefined;
|
||||||
|
switch (context) {
|
||||||
|
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 undefined:
|
||||||
|
// Image API 1.0 has no '@context'
|
||||||
|
if (getComplianceLevelOfImageInfoForVersion(imageInfo, Versions.VERSION1)) {
|
||||||
|
return Versions.VERSION1;
|
||||||
|
}
|
||||||
|
// TODO error: can't detect Image API version
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// TODO error: can't detect Image API version
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLevelProfileForImageInfo(imageInfo) {
|
||||||
|
const version = getVersionOfImageInfo(imageInfo),
|
||||||
|
complianceLevel = getComplianceLevelOfImageInfoForVersion(imageInfo, version);
|
||||||
|
let level;
|
||||||
|
if (version === undefined || complianceLevel === undefined) {
|
||||||
|
return IIIF_PROFILE_VALUES.none;
|
||||||
|
}
|
||||||
|
level = complianceLevel.match(/level[0-2](\.json)?$/g);
|
||||||
|
level = Array.isArray(level) ? level[0].replace('.json', '') : 'none';
|
||||||
|
return IIIF_PROFILE_VALUES[version][level];
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVersion1Options(imageInfo) {
|
||||||
|
const levelProfile = getLevelProfileForImageInfo(imageInfo);
|
||||||
|
return {
|
||||||
|
url: imageInfo['@id'].replace(/\/?(info.json)?$/g, ''),
|
||||||
|
features: levelProfile.features,
|
||||||
|
formats: [...levelProfile.formats, imageInfo.formats === undefined ?
|
||||||
|
[] : imageInfo.formats
|
||||||
|
],
|
||||||
|
qualities: [...levelProfile.qualities, imageInfo.qualities === undefined ?
|
||||||
|
[] : imageInfo.qualities
|
||||||
|
],
|
||||||
|
resolutions: imageInfo.scale_factors,
|
||||||
|
tileSize: imageInfo.tile_width !== undefined ? imageInfo.tile_height != undefined ?
|
||||||
|
[imageInfo.tile_width, imageInfo.tile_height] : [imageInfo.tile_width, imageInfo.tile_width] :
|
||||||
|
imageInfo.tile_height != undefined ? [imageInfo.tile_height, imageInfo.tile_height] : undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVersion2Options(imageInfo) {
|
||||||
|
const levelProfile = getLevelProfileForImageInfo(imageInfo),
|
||||||
|
additionalProfile = Array.isArray(imageInfo.profile) && imageInfo.profile.length > 1,
|
||||||
|
profileFeatures = additionalProfile && imageInfo.profile[1].supports ? imageInfo.profile[1].supports : [],
|
||||||
|
profileFormats = additionalProfile && imageInfo.profile[1].formats ? imageInfo.profile[1].formats : [],
|
||||||
|
profileQualities = additionalProfile && imageInfo.profile[1].qualities ? imageInfo.profile[1].qualities : [],
|
||||||
|
attributions = [];
|
||||||
|
if (imageInfo.attribution !== undefined) {
|
||||||
|
// TODO potentially dangerous
|
||||||
|
attributions.push(imageInfo.attribution);
|
||||||
|
}
|
||||||
|
if (imageInfo.license !== undefined) {
|
||||||
|
let license = imageInfo.license;
|
||||||
|
if (license.match(/^http(s)?:\/\//g)) {
|
||||||
|
license = '<a href="' + encodeURI(license) + '"/>' + encodeURI(license) + '</a>';
|
||||||
|
}
|
||||||
|
// TODO potentially dangerous
|
||||||
|
attributions.push(license);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: imageInfo['@id'].replace(/\/?(info.json)?$/g, ''),
|
||||||
|
sizes: imageInfo.sizes === undefined ? undefined : imageInfo.sizes.map(function(size) {
|
||||||
|
return [size.width, size.height];
|
||||||
|
}),
|
||||||
|
tileSize: imageInfo.tiles === undefined ? undefined : [
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.width;
|
||||||
|
})[0],
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.height;
|
||||||
|
})[0]
|
||||||
|
],
|
||||||
|
resolutions: imageInfo.tiles === undefined ? undefined :
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.scaleFactors;
|
||||||
|
})[0],
|
||||||
|
features: [...levelProfile.features, ...profileFeatures],
|
||||||
|
formats: [...levelProfile.formats, ...profileFormats],
|
||||||
|
qualities: [...levelProfile.qualities, ...profileQualities],
|
||||||
|
attributions: attributions.length == 0 ? undefined : attributions
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateVersion3Options(imageInfo) {
|
||||||
|
const levelProfile = getLevelProfileForImageInfo(imageInfo);
|
||||||
|
return {
|
||||||
|
url: imageInfo['id'],
|
||||||
|
sizes: imageInfo.sizes === undefined ? undefined : imageInfo.sizes.map(function(size) {
|
||||||
|
return [size.width, size.height];
|
||||||
|
}),
|
||||||
|
tileSize: imageInfo.tiles === undefined ? undefined : [
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.width;
|
||||||
|
})[0],
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.height;
|
||||||
|
})[0]
|
||||||
|
],
|
||||||
|
resolutions: imageInfo.tiles === undefined ? undefined :
|
||||||
|
imageInfo.tiles.map(function(tile) {
|
||||||
|
return tile.scaleFactors;
|
||||||
|
})[0],
|
||||||
|
features: imageInfo.extraFeatures === undefined ? levelProfile.features :
|
||||||
|
[...levelProfile.features, ...imageInfo.extraFeatures],
|
||||||
|
formats: imageInfo.extraFormats === undefined ? levelProfile.formats :
|
||||||
|
[...levelProfile.formats, ...imageInfo.extraFormats],
|
||||||
|
qualities: imageInfo.extraQualities === undefined ? levelProfile.qualities :
|
||||||
|
[...levelProfile.extraQualities, ...imageInfo.extraQualities],
|
||||||
|
maxWidth: undefined,
|
||||||
|
maxHeight: undefined,
|
||||||
|
maxArea: undefined,
|
||||||
|
attributions: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const versionFunctions = {};
|
||||||
|
versionFunctions[Versions.VERSION1] = generateVersion1Options;
|
||||||
|
versionFunctions[Versions.VERSION2] = generateVersion2Options;
|
||||||
|
versionFunctions[Versions.VERSION3] = generateVersion3Options;
|
||||||
|
|
||||||
|
function getOptionsForImageInformation(imageInfo, preferredOptions) {
|
||||||
|
const options = preferredOptions || {},
|
||||||
|
version = getVersionOfImageInfo(imageInfo),
|
||||||
|
optionAttributions = options.attributions ? options.attributions : [],
|
||||||
|
imageOptions = version === undefined ? undefined : versionFunctions[version](imageInfo);
|
||||||
|
if (imageOptions === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
url: options.url ? options.url : imageOptions.url,
|
||||||
|
version: version,
|
||||||
|
size: [imageInfo.width, imageInfo.height],
|
||||||
|
sizes: imageOptions.sizes,
|
||||||
|
format: imageOptions.formats.includes(options.format) ? options.format : 'jpg',
|
||||||
|
features: imageOptions.features,
|
||||||
|
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,
|
||||||
|
attributions: [
|
||||||
|
...optionAttributions,
|
||||||
|
...(imageOptions.attributions === undefined ? [] : imageOptions.attributions)
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO at the moment, this does not need to be a class.
|
||||||
|
class IIIFInfo {
|
||||||
|
readFromJson(imageInfo, preferredOptions) {
|
||||||
|
return getOptionsForImageInformation(imageInfo, preferredOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default IIIFInfo;
|
||||||
@@ -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';
|
||||||
|
|||||||
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