UNPKG

@allmaps/transform

Version:

Coordinate transformation functions

126 lines (125 loc) 4.72 kB
import { newArrayMatrix, pasteArrayMatrix, arrayMatrixSize } from '@allmaps/stdlib'; import { BaseLinearWeightsTransformation } from './BaseLinearWeightsTransformation.js'; import { solveJointlyPseudoInverse } from '../shared/solve-functions.js'; /** * 2D Helmert transformation (= similarity transformation) * * This transformation is a composition of a translation, rotation and scaling. There is no shearing. * * For this transformations, the system of equations is solved for x and y jointly. */ export class Helmert extends BaseLinearWeightsTransformation { coefsArrayMatrices; coefsArrayMatricesSize; weightsArray; weightsArrays; constructor(sourcePoints, destinationPoints) { super(sourcePoints, destinationPoints, 'helmert', 2); this.coefsArrayMatrices = this.getCoefsArrayMatrices(); this.coefsArrayMatricesSize = this.coefsArrayMatrices.map((coefsArrayMatrix) => arrayMatrixSize(coefsArrayMatrix)); } getDestinationPointsArrays() { return [ this.destinationPoints.map((value) => value[0]), this.destinationPoints.map((value) => value[1]) ]; } getCoefsArrayMatrices() { let coefsArrayMatrix0 = newArrayMatrix(this.pointCount, 4, 0); let coefsArrayMatrix1 = newArrayMatrix(this.pointCount, 4, 0); for (let i = 0; i < this.pointCount; i++) { const sourcePointCoefsArrays = this.getSourcePointCoefsArrays(this.sourcePoints[i]); coefsArrayMatrix0 = pasteArrayMatrix(coefsArrayMatrix0, i, 0, [ sourcePointCoefsArrays[0] ]); coefsArrayMatrix1 = pasteArrayMatrix(coefsArrayMatrix1, i, 0, [ sourcePointCoefsArrays[1] ]); } return [coefsArrayMatrix0, coefsArrayMatrix1]; } /** * Get two 1x4 coefsArrays, populating the 2Nx4 coefsArrayMatrices * 1 0 x0 -y0 * 1 0 x1 -y1 * ... * 0 1 y0 x0 * 0 1 y1 x1 * ... * * @param sourcePoint */ getSourcePointCoefsArrays(sourcePoint) { return [ [1, 0, sourcePoint[0], -sourcePoint[1]], [0, 1, sourcePoint[1], sourcePoint[0]] ]; } solve() { this.weightsArray = solveJointlyPseudoInverse(this.coefsArrayMatrices, this.destinationPointsArrays); this.weightsArrays = [this.weightsArray, this.weightsArray]; } getMeasures() { if (!this.weightsArrays) { this.solve(); } if (!this.weightsArray) { throw new Error('Helmert weights not computed'); } const measures = {}; measures.scale = Math.sqrt(this.weightsArray[2] ** 2 + this.weightsArray[3] ** 2); measures.rotation = Math.atan2(this.weightsArray[3], this.weightsArray[2]); measures.translation = [this.weightsArray[0], this.weightsArray[1]]; return measures; } evaluateFunction(newSourcePoint) { if (!this.weightsArrays) { this.solve(); } if (!this.weightsArray) { throw new Error('Helmert weights not computed'); } const newDestinationPoint = [ this.weightsArray[0] + this.weightsArray[2] * newSourcePoint[0] - this.weightsArray[3] * newSourcePoint[1], this.weightsArray[1] + this.weightsArray[2] * newSourcePoint[1] + this.weightsArray[3] * newSourcePoint[0] ]; // Alternatively, using derived helmert measures // this.translation[0] + // this.scale * Math.cos(rotation) * newSourcePoint[0] - // this.scale * Math.sin(rotation) * newSourcePoint[1], // this.translation[1] + // this.scale * Math.cos(rotation) * newSourcePoint[1] + // this.scale * Math.sin(rotation) * newSourcePoint[0] return newDestinationPoint; } evaluatePartialDerivativeX(_newSourcePoint) { if (!this.weightsArrays) { this.solve(); } if (!this.weightsArray) { throw new Error('Helmert weights not computed'); } const newDestinationPointPartDerX = [ this.weightsArray[2], this.weightsArray[3] ]; return newDestinationPointPartDerX; } evaluatePartialDerivativeY(_newSourcePoint) { if (!this.weightsArrays) { this.solve(); } if (!this.weightsArray) { throw new Error('Helmert weights not computed'); } const newDestinationPointPartDerY = [ -this.weightsArray[3], this.weightsArray[2] ]; return newDestinationPointPartDerY; } }