/** * @module ol/transform */ import {WORKER_OFFSCREEN_CANVAS} from './has.js'; import {assert} from './asserts.js'; /** * An array representing an affine 2d transformation for use with * {@link module:ol/transform} functions. The array has 6 elements. * @typedef {!Array} Transform * @api */ /** * Collection of affine 2d transformation functions. The functions work on an * array of 6 elements. The element order is compatible with the [SVGMatrix * interface](https://developer.mozilla.org/en-US/docs/Web/API/SVGMatrix) and is * a subset (elements a to f) of a 3×3 matrix: * ``` * [ a c e ] * [ b d f ] * [ 0 0 1 ] * ``` */ /** * @private * @type {Transform} */ const tmp_ = new Array(6); /** * Create an identity transform. * @return {!Transform} Identity transform. */ export function create() { return [1, 0, 0, 1, 0, 0]; } /** * Resets the given transform to an identity transform. * @param {!Transform} transform Transform. * @return {!Transform} Transform. */ export function reset(transform) { return set(transform, 1, 0, 0, 1, 0, 0); } /** * Multiply the underlying matrices of two transforms and return the result in * the first transform. * @param {!Transform} transform1 Transform parameters of matrix 1. * @param {!Transform} transform2 Transform parameters of matrix 2. * @return {!Transform} transform1 multiplied with transform2. */ export function multiply(transform1, transform2) { const a1 = transform1[0]; const b1 = transform1[1]; const c1 = transform1[2]; const d1 = transform1[3]; const e1 = transform1[4]; const f1 = transform1[5]; const a2 = transform2[0]; const b2 = transform2[1]; const c2 = transform2[2]; const d2 = transform2[3]; const e2 = transform2[4]; const f2 = transform2[5]; transform1[0] = a1 * a2 + c1 * b2; transform1[1] = b1 * a2 + d1 * b2; transform1[2] = a1 * c2 + c1 * d2; transform1[3] = b1 * c2 + d1 * d2; transform1[4] = a1 * e2 + c1 * f2 + e1; transform1[5] = b1 * e2 + d1 * f2 + f1; return transform1; } /** * Set the transform components a-f on a given transform. * @param {!Transform} transform Transform. * @param {number} a The a component of the transform. * @param {number} b The b component of the transform. * @param {number} c The c component of the transform. * @param {number} d The d component of the transform. * @param {number} e The e component of the transform. * @param {number} f The f component of the transform. * @return {!Transform} Matrix with transform applied. */ export function set(transform, a, b, c, d, e, f) { transform[0] = a; transform[1] = b; transform[2] = c; transform[3] = d; transform[4] = e; transform[5] = f; return transform; } /** * Set transform on one matrix from another matrix. * @param {!Transform} transform1 Matrix to set transform to. * @param {!Transform} transform2 Matrix to set transform from. * @return {!Transform} transform1 with transform from transform2 applied. */ export function setFromArray(transform1, transform2) { transform1[0] = transform2[0]; transform1[1] = transform2[1]; transform1[2] = transform2[2]; transform1[3] = transform2[3]; transform1[4] = transform2[4]; transform1[5] = transform2[5]; return transform1; } /** * Transforms the given coordinate with the given transform returning the * resulting, transformed coordinate. The coordinate will be modified in-place. * * @param {Transform} transform The transformation. * @param {import("./coordinate.js").Coordinate|import("./pixel.js").Pixel} coordinate The coordinate to transform. * @return {import("./coordinate.js").Coordinate|import("./pixel.js").Pixel} return coordinate so that operations can be * chained together. */ export function apply(transform, coordinate) { const x = coordinate[0]; const y = coordinate[1]; coordinate[0] = transform[0] * x + transform[2] * y + transform[4]; coordinate[1] = transform[1] * x + transform[3] * y + transform[5]; return coordinate; } /** * Applies rotation to the given transform. * @param {!Transform} transform Transform. * @param {number} angle Angle in radians. * @return {!Transform} The rotated transform. */ export function rotate(transform, angle) { const cos = Math.cos(angle); const sin = Math.sin(angle); return multiply(transform, set(tmp_, cos, sin, -sin, cos, 0, 0)); } /** * Applies scale to a given transform. * @param {!Transform} transform Transform. * @param {number} x Scale factor x. * @param {number} y Scale factor y. * @return {!Transform} The scaled transform. */ export function scale(transform, x, y) { return multiply(transform, set(tmp_, x, 0, 0, y, 0, 0)); } /** * Creates a scale transform. * @param {!Transform} target Transform to overwrite. * @param {number} x Scale factor x. * @param {number} y Scale factor y. * @return {!Transform} The scale transform. */ export function makeScale(target, x, y) { return set(target, x, 0, 0, y, 0, 0); } /** * Applies translation to the given transform. * @param {!Transform} transform Transform. * @param {number} dx Translation x. * @param {number} dy Translation y. * @return {!Transform} The translated transform. */ export function translate(transform, dx, dy) { return multiply(transform, set(tmp_, 1, 0, 0, 1, dx, dy)); } /** * Creates a composite transform given an initial translation, scale, rotation, and * final translation (in that order only, not commutative). * @param {!Transform} transform The transform (will be modified in place). * @param {number} dx1 Initial translation x. * @param {number} dy1 Initial translation y. * @param {number} sx Scale factor x. * @param {number} sy Scale factor y. * @param {number} angle Rotation (in counter-clockwise radians). * @param {number} dx2 Final translation x. * @param {number} dy2 Final translation y. * @return {!Transform} The composite transform. */ export function compose(transform, dx1, dy1, sx, sy, angle, dx2, dy2) { const sin = Math.sin(angle); const cos = Math.cos(angle); transform[0] = sx * cos; transform[1] = sy * sin; transform[2] = -sx * sin; transform[3] = sy * cos; transform[4] = dx2 * sx * cos - dy2 * sx * sin + dx1; transform[5] = dx2 * sy * sin + dy2 * sy * cos + dy1; return transform; } /** * Creates a composite transform given an initial translation, scale, rotation, and * final translation (in that order only, not commutative). The resulting transform * string can be applied as `transform` property of an HTMLElement's style. * @param {number} dx1 Initial translation x. * @param {number} dy1 Initial translation y. * @param {number} sx Scale factor x. * @param {number} sy Scale factor y. * @param {number} angle Rotation (in counter-clockwise radians). * @param {number} dx2 Final translation x. * @param {number} dy2 Final translation y. * @return {string} The composite css transform. * @api */ export function composeCssTransform(dx1, dy1, sx, sy, angle, dx2, dy2) { return toString(compose(create(), dx1, dy1, sx, sy, angle, dx2, dy2)); } /** * Invert the given transform. * @param {!Transform} source The source transform to invert. * @return {!Transform} The inverted (source) transform. */ export function invert(source) { return makeInverse(source, source); } /** * Invert the given transform. * @param {!Transform} target Transform to be set as the inverse of * the source transform. * @param {!Transform} source The source transform to invert. * @return {!Transform} The inverted (target) transform. */ export function makeInverse(target, source) { const det = determinant(source); assert(det !== 0, 32); // Transformation matrix cannot be inverted const a = source[0]; const b = source[1]; const c = source[2]; const d = source[3]; const e = source[4]; const f = source[5]; target[0] = d / det; target[1] = -b / det; target[2] = -c / det; target[3] = a / det; target[4] = (c * f - d * e) / det; target[5] = -(a * f - b * e) / det; return target; } /** * Returns the determinant of the given matrix. * @param {!Transform} mat Matrix. * @return {number} Determinant. */ export function determinant(mat) { return mat[0] * mat[3] - mat[1] * mat[2]; } /** * @type {HTMLElement} * @private */ let transformStringDiv; /** * A rounded string version of the transform. This can be used * for CSS transforms. * @param {!Transform} mat Matrix. * @return {string} The transform as a string. */ export function toString(mat) { const transformString = 'matrix(' + mat.join(', ') + ')'; if (WORKER_OFFSCREEN_CANVAS) { return transformString; } const node = transformStringDiv || (transformStringDiv = document.createElement('div')); node.style.transform = transformString; return node.style.transform; }