/**
* @module og/layer/XYZ
*/
"use strict";
import * as mercator from "../mercator.js";
import { EPSG3857 } from "../proj/EPSG3857.js";
import { Layer } from "./Layer.js";
import { stringTemplate } from "../utils/shared.js";
import { RENDERING } from "../quadTree/quadTree.js";
/**
* Represents an imagery tiles source provider.
* @class
* @extends {Layer}
* @param {string} name - Layer name.
* @param {Object} options:
* @param {number} [options.opacity=1.0] - Layer opacity.
* @param {Array.<string>} [options.subdomains=['a','b','c']] - Subdomains of the tile service.
* @param {number} [options.minZoom=0] - Minimal visibility zoom level.
* @param {number} [options.maxZoom=0] - Maximal visibility zoom level.
* @param {number} [options.minNativeZoom=0] - Minimal available zoom level.
* @param {number} [options.maxNativeZoom=19] - Maximal available zoom level.
* @param {string} [options.attribution] - Layer attribution that displayed in the attribution area on the screen.
* @param {boolean} [options.isBaseLayer=false] - Base layer flag.
* @param {boolean} [options.visibility=true] - Layer visibility.
* @param {string} [options.crossOrigin=true] - If true, all tiles will have their crossOrigin attribute set to ''.
* @param {string} options.url - Tile url source template(see example below).
* @param {string} options.textureFilter - texture gl filter. NEAREST, LINEAR, MIPMAP, ANISOTROPHIC.
* @param {layer.XYZ~_urlRewriteCallback} options.urlRewrite - Url rewrite function.
* @fires og.layer.XYZ#load
* @fires og.layer.XYZ#loadend
*
* @example <caption>Creates OpenStreetMap base tile layer</caption>
* new og.layer.XYZ("OpenStreetMap", {
* isBaseLayer: true,
* url: "http://b.tile.openstreetmap.org/{z}/{x}/{y}.png",
* visibility: true,
* attribution: 'Data @ <a href="http://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="http://www.openstreetmap.org/copyright">ODbL</a>'
* });
*/
class XYZ extends Layer {
/**
* @param {string} name - Layer name.
* @param {*} options
*/
constructor(name, options) {
super(name, options);
this.events.registerNames(EVENT_NAMES);
/**
* Tile url source template.
* @public
* @type {string}
*/
this.url = options.url || "";
/**
* @protected
*/
this._s = options.subdomains || ["a", "b", "c"];
/**
* Minimal native zoom level when tiles are available.
* @public
* @type {number}
*/
this.minNativeZoom = options.minNativeZoom || 0;
/**
* Maximal native zoom level when tiles are available.
* @public
* @type {number}
*/
this.maxNativeZoom = options.maxNativeZoom || 19;
/**
* @protected
*/
this._crossOrigin = options.crossOrigin === undefined ? "" : options.crossOrigin;
/**
* Rewrites imagery tile url query.
* @private
* @callback og.layer.XYZ~_urlRewriteCallback
* @param {Segment} segment - Segment to load.
* @param {string} url - Created url.
* @returns {string} - Url query string.
*/
this._urlRewriteCallback = options.urlRewrite || null;
}
get instanceName() {
return "XYZ";
}
/**
* Abort loading tiles.
* @public
*/
abortLoading() {
if (this._planet) {
this._planet._tileLoader.abort();
}
}
/**
* Sets layer visibility.
* @public
* @param {boolean} visibility - Layer visibility.
*/
setVisibility(visibility) {
if (visibility !== this._visibility) {
super.setVisibility(visibility);
if (!visibility) {
this.abortLoading();
}
}
}
/**
* Sets imagery tiles url source template.
* @public
* @param {string} url - Url template.
* @example
* http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png
* where {z}, {x} and {y} - replaces by current tile values, {s} - random domen.
*/
setUrl(url) {
this.url = url;
}
_checkSegment(segment) {
return segment._projection.id === EPSG3857.id;
}
/**
* Start to load tile material.
* @public
* @virtual
* @param {Material} material - Loads current material.
*/
loadMaterial(material, forceLoading) {
let seg = material.segment;
if (this._isBaseLayer) {
material.texture = seg.getDefaultTexture();
} else {
material.texture = seg.planet.transparentTexture;
}
// Q: Maybe we should change "<2" to material.segment.tileZoom < (material.layer.minZoom + 1)
if (this._planet.layerLock.isFree() || material.segment.tileZoom < 2) {
material.isReady = false;
material.isLoading = true;
if (this._checkSegment(seg)) {
material.loadingAttempts++;
this._planet._tileLoader.load(
{
src: this._getHTTPRequestString(material.segment),
type: "imageBitmap",
filter: () =>
(seg.initialized && seg.node.getState() === RENDERING) || forceLoading,
options: {}
},
(response) => {
if (response.status === "ready") {
if (material.isLoading) {
let e = this.events.load;
if (e.handlers.length) {
this.events.dispatch(e, material);
}
material.applyImage(response.data);
response.data = null;
}
} else if (response.status === "abort") {
material.isLoading = false;
} else if (response.status === "error") {
if (material.isLoading) {
material.textureNotExists();
}
}
}
);
} else {
material.textureNotExists();
}
}
}
/**
* Creates query url.
* @protected
* @virtual
* @param {Segment} segment - Creates specific url for current segment.
* @returns {String} - Returns url string.
*/
_createUrl(segment) {
return stringTemplate(this.url, {
s: this._getSubdomain(),
x: segment.tileX.toString(),
y: segment.tileY.toString(),
z: segment.tileZoom.toString()
});
}
_getSubdomain() {
return this._s[Math.floor(Math.random() * this._s.length)];
}
/**
* Returns actual url query string.
* @protected
* @param {Segment} segment - Segment that loads image data.
* @returns {string} - Url string.
*/
_getHTTPRequestString(segment) {
return this._urlRewriteCallback
? this._urlRewriteCallback(segment, this.url)
: this._createUrl(segment);
}
/**
* Sets url rewrite callback, used for custom url rewriting for every tile laoding.
* @public
* @param {layer.XYZ~_urlRewriteCallback} ur - The callback that returns tile custom created url.
*/
setUrlRewriteCallback(ur) {
this._urlRewriteCallback = ur;
}
applyMaterial(material) {
if (material.isReady) {
return material.texOffset;
} else {
let segment = material.segment,
pn = segment.node,
notEmpty = false;
let mId = this._id;
let psegm = material;
while (pn.parentNode) {
pn = pn.parentNode;
psegm = pn.segment.materials[mId];
if (psegm && psegm.textureExists) {
notEmpty = true;
break;
}
}
if (segment.passReady) {
let maxNativeZoom = material.layer.maxNativeZoom;
if (pn.segment.tileZoom === maxNativeZoom) {
material.textureNotExists();
} else if (material.segment.tileZoom <= maxNativeZoom) {
!material.isLoading && !material.isReady && this.loadMaterial(material);
} else {
let pn = segment.node;
while (pn.segment.tileZoom > material.layer.maxNativeZoom) {
pn = pn.parentNode;
}
let pnm = pn.segment.materials[material.layer._id];
if (pnm) {
!pnm.isLoading && !pnm.isReady && this.loadMaterial(pnm, true);
} else {
pnm = pn.segment.materials[material.layer._id] = material.layer.createMaterial(
pn.segment
);
this.loadMaterial(pnm, true);
}
}
}
if (notEmpty) {
material.appliedNode = pn;
material.appliedNodeId = pn.nodeId;
material.texture = psegm.texture;
let dZ2 = 1.0 / (2 << (segment.tileZoom - pn.segment.tileZoom - 1));
material.texOffset[0] = segment.tileX * dZ2 - pn.segment.tileX;
material.texOffset[1] = segment.tileY * dZ2 - pn.segment.tileY;
material.texOffset[2] = dZ2;
material.texOffset[3] = dZ2;
} else {
material.texture = segment.planet.transparentTexture;
material.texOffset[0] = 0.0;
material.texOffset[1] = 0.0;
material.texOffset[2] = 1.0;
material.texOffset[3] = 1.0;
}
}
return material.texOffset;
}
clearMaterial(material) {
if (material.isReady && material.textureExists) {
!material.texture.default &&
material.segment.handler.gl.deleteTexture(material.texture);
material.texture = null;
if (material.image) {
material.image.src = "";
material.image = null;
}
}
material.isReady = false;
material.textureExists = false;
material.isLoading = false;
}
/**
* @protected
*/
_correctFullExtent() {
var e = this._extent,
em = this._extentMerc;
var ENLARGE_MERCATOR_LON = mercator.POLE + 50000;
var ENLARGE_MERCATOR_LAT = mercator.POLE + 50000;
if (e.northEast.lat === 90.0) {
em.northEast.lat = ENLARGE_MERCATOR_LAT;
}
if (e.northEast.lon === 180.0) {
em.northEast.lon = ENLARGE_MERCATOR_LON;
}
if (e.southWest.lat === -90.0) {
em.southWest.lat = -ENLARGE_MERCATOR_LAT;
}
if (e.southWest.lon === -180.0) {
em.southWest.lon = -ENLARGE_MERCATOR_LON;
}
if (e.northEast.lat >= mercator.MAX_LAT) {
e.northEast.lat = mercator.MAX_LAT;
}
if (e.northEast.lat <= mercator.MIN_LAT) {
e.northEast.lat = mercator.MIN_LAT;
}
}
}
const EVENT_NAMES = [
/**
* Triggered when current tile image has loaded before rendereing.
* @event og.layer.XYZ#load
*/
"load",
/**
* Triggered when all tiles have loaded or loading has stopped.
* @event og.layer.XYZ#loadend
*/
"loadend"
];
export { XYZ };