@allmaps/transform
Version:
Coordinate transformation functions
126 lines (125 loc) • 4.72 kB
JavaScript
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;
}
}