211 lines
6.9 KiB
JavaScript
211 lines
6.9 KiB
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;
|