ol
Version:
OpenLayers mapping library
211 lines (187 loc) • 7.1 kB
JavaScript
/**
* @module ol/source/ImageTile
*/
import {expandUrl, pickUrl, renderXYZTemplate} from '../uri.js';
import DataTileSource from './DataTile.js';
/**
* Image tile loading function. The function is called with z, x, and y tile coordinates and
* returns an {@link import("../DataTile.js").ImageLike image} or a promise for the same.
*
* @typedef {function(number, number, number, import("./DataTile.js").LoaderOptions):(import("../DataTile.js").ImageLike|Promise<import("../DataTile.js").ImageLike>)} Loader
*/
/**
* @typedef {function(number, number, number, import("./DataTile.js").LoaderOptions):string} UrlGetter
*/
/**
* @typedef {string | Array<string> | UrlGetter} UrlLike
*/
/**
* @typedef {Object} Options
* @property {UrlLike} [url] The image URL template. In addition to a single URL template, an array of URL templates or a function
* can be provided. If a function is provided, it will be called with z, x, y tile coordinates and loader options and should
* return a URL.
* @property {Loader} [loader] Data loader. Called with z, x, and y tile coordinates.
* Returns an {@link import("../DataTile.js").ImageLike image} for a tile or a promise for the same.
* The promise should not resolve until the image is loaded. If the `url` option is provided, a loader will be created.
* @property {import("./Source.js").AttributionLike} [attributions] Attributions.
* @property {boolean} [attributionsCollapsible=true] Attributions are collapsible.
* @property {number} [maxZoom=42] Optional max zoom level. Not used if `tileGrid` is provided.
* @property {number} [minZoom=0] Optional min zoom level. Not used if `tileGrid` is provided.
* @property {number|import("../size.js").Size} [tileSize=[256, 256]] The pixel width and height of the source tiles.
* This may be different than the rendered pixel size if a `tileGrid` is provided.
* @property {number} [gutter=0] The size in pixels of the gutter around data tiles to ignore.
* This allows artifacts of rendering at tile edges to be ignored.
* Supported data should be wider and taller than the tile size by a value of `2 x gutter`.
* @property {number} [maxResolution] Optional tile grid resolution at level zero. Not used if `tileGrid` is provided.
* @property {import("../proj.js").ProjectionLike} [projection='EPSG:3857'] Tile projection.
* @property {import("../tilegrid/TileGrid.js").default} [tileGrid] Tile grid.
* @property {import("./Source.js").State} [state] The source state.
* @property {boolean} [wrapX=true] Render tiles beyond the antimeridian.
* @property {number} [transition] Transition time when fading in new tiles (in miliseconds).
* @property {boolean} [interpolate=true] Use interpolated values when resampling.
* @property {import('./DataTile.js').CrossOriginAttribute} [crossOrigin='anonymous'] The crossOrigin property to pass to loaders for image data.
* @property {number|import("../array.js").NearestDirectionFunction} [zDirection=0]
* Choose whether to use tiles with a higher or lower zoom level when between integer
* zoom levels. See {@link module:ol/tilegrid/TileGrid~TileGrid#getZForResolution}.
*/
const loadError = new Error('Image failed to load');
/**
* @param {string} template The image url template.
* @param {number} z The tile z coordinate.
* @param {number} x The tile x coordinate.
* @param {number} y The tile y coordinate.
* @param {import('./DataTile.js').LoaderOptions} options The loader options.
* @return {Promise<HTMLImageElement>} Resolves with a loaded image.
*/
function loadImage(template, z, x, y, options) {
return new Promise((resolve, reject) => {
const image = new Image();
image.crossOrigin = options.crossOrigin ?? null;
image.addEventListener('load', () => resolve(image));
image.addEventListener('error', () => reject(loadError));
image.src = renderXYZTemplate(template, z, x, y, options.maxY);
});
}
/**
* @param {Array<string>} templates The url templates.
* @return {Loader} The image loader.
*/
function makeLoaderFromTemplates(templates) {
return function (z, x, y, options) {
const template = pickUrl(templates, z, x, y);
return loadImage(template, z, x, y, options);
};
}
/**
* @param {UrlGetter} getter The url getter.
* @return {Loader} The image loader.
*/
function makeLoaderFromGetter(getter) {
return function (z, x, y, options) {
const url = getter(z, x, y, options);
return loadImage(url, z, x, y, options);
};
}
/**
* @param {UrlLike} url The URL-like option.
* @return {Loader} The tile loader.
*/
function makeLoaderFromUrlLike(url) {
/**
* @type {Loader}
*/
let loader;
if (Array.isArray(url)) {
loader = makeLoaderFromTemplates(url);
} else if (typeof url === 'string') {
const urls = expandUrl(url);
loader = makeLoaderFromTemplates(urls);
} else if (typeof url === 'function') {
loader = makeLoaderFromGetter(url);
} else {
throw new Error(
'The url option must be a single template, an array of templates, or a function for getting a URL',
);
}
return loader;
}
let keyCount = 0;
/**
* @param {UrlLike} url The URL-like option.
* @return {string} A key for the URL.
*/
function keyFromUrlLike(url) {
if (Array.isArray(url)) {
return url.join('\n');
}
if (typeof url === 'string') {
return url;
}
++keyCount;
return 'url-function-key-' + keyCount;
}
/**
* @classdesc
* A source for typed array data tiles.
*
* @extends DataTileSource<import("../ImageTile.js").default>
* @fires import("./Tile.js").TileSourceEvent
* @api
*/
class ImageTileSource extends DataTileSource {
/**
* @param {Options} [options] DataTile source options.
*/
constructor(options) {
options = options || {};
/**
* @type {Loader}
*/
let loader = options.loader;
/**
* @type {string}
*/
let key;
if (options.url) {
loader = makeLoaderFromUrlLike(options.url);
key = keyFromUrlLike(options.url);
}
/**
* @type {import('./Source.js').State}
*/
const state = !loader ? 'loading' : options.state;
const wrapX = options.wrapX === undefined ? true : options.wrapX;
super({
loader: loader,
key: key,
attributions: options.attributions,
attributionsCollapsible: options.attributionsCollapsible,
maxZoom: options.maxZoom,
minZoom: options.minZoom,
tileSize: options.tileSize,
gutter: options.gutter,
maxResolution: options.maxResolution,
projection: options.projection,
tileGrid: options.tileGrid,
state: state,
wrapX: wrapX,
transition: options.transition,
interpolate: options.interpolate !== false,
crossOrigin: options.crossOrigin,
zDirection: options.zDirection,
});
}
/**
* @param {UrlLike} url The new URL.
* @api
*/
setUrl(url) {
const loader = makeLoaderFromUrlLike(url);
this.setLoader(loader);
this.setKey(keyFromUrlLike(url));
if (this.getState() !== 'ready') {
this.setState('ready');
}
}
}
export default ImageTileSource;