@allmaps/transform
Advanced tools
Comparing version 1.0.0-beta.37 to 1.0.0-beta.38
import GcpTransformer from './transformer.js'; | ||
import Transformation from './transformation.js'; | ||
import Helmert from './shared/helmert.js'; | ||
import Polynomial from './shared/polynomial.js'; | ||
import Projective from './shared/projective.js'; | ||
import RBF from './shared/radial-basis-function.js'; | ||
import Straight from './shared/straight.js'; | ||
import { supportedDistortionMeasures, computeDistortionFromPartialDerivatives } from './distortion.js'; | ||
/** @module allmaps/transform */ | ||
export { GcpTransformer }; | ||
export { GcpTransformer, Transformation, Helmert, Polynomial, Projective, RBF, Straight, supportedDistortionMeasures, computeDistortionFromPartialDerivatives }; | ||
export * from './shared/types.js'; |
import GcpTransformer from './transformer.js'; | ||
import Transformation from './transformation.js'; | ||
import Helmert from './shared/helmert.js'; | ||
import Polynomial from './shared/polynomial.js'; | ||
import Projective from './shared/projective.js'; | ||
import RBF from './shared/radial-basis-function.js'; | ||
import Straight from './shared/straight.js'; | ||
import { supportedDistortionMeasures, computeDistortionFromPartialDerivatives } from './distortion.js'; | ||
/** @module allmaps/transform */ | ||
export { GcpTransformer }; | ||
// TODO: consider only exporting GCPTransformer | ||
export { GcpTransformer, Transformation, Helmert, Polynomial, Projective, RBF, Straight, supportedDistortionMeasures, computeDistortionFromPartialDerivatives }; | ||
export * from './shared/types.js'; |
import { Matrix } from 'ml-matrix'; | ||
import type { Transformation } from './types'; | ||
import Transformation from '../transformation.js'; | ||
import type { Point } from '@allmaps/types'; | ||
export default class Helmert implements Transformation { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
export default class Helmert extends Transformation { | ||
helmertParametersMatrix: Matrix; | ||
@@ -12,5 +10,6 @@ helmertParameters: number[]; | ||
translation?: Point; | ||
pointCount: number; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[]); | ||
interpolate(newSourcePoint: Point): Point; | ||
evaluateFunction(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeX(_newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeY(_newSourcePoint: Point): Point; | ||
} |
import { Matrix, pseudoInverse } from 'ml-matrix'; | ||
export default class Helmert { | ||
import Transformation from '../transformation.js'; | ||
export default class Helmert extends Transformation { | ||
helmertParametersMatrix; | ||
helmertParameters; | ||
scale; | ||
rotation; | ||
translation; | ||
constructor(sourcePoints, destinationPoints) { | ||
this.sourcePoints = sourcePoints; | ||
this.destinationPoints = destinationPoints; | ||
this.pointCount = this.sourcePoints.length; | ||
if (this.pointCount < 2) { | ||
throw new Error('Not enough control points. A helmert transformation requires a minimum of 2 points, but ' + | ||
this.pointCount + | ||
' are given.'); | ||
} | ||
super(sourcePoints, destinationPoints, 'helmert', 2); | ||
// 2D Helmert transformation (= similarity transformation) | ||
@@ -27,10 +26,10 @@ // This solution uses the 'Pseudo Inverse' for estimating a least-square solution, see https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse | ||
helmertCoefsMatrix.set(2 * i, 1, 0); | ||
helmertCoefsMatrix.set(2 * i, 2, sourcePoints[i][0]); | ||
helmertCoefsMatrix.set(2 * i, 3, -sourcePoints[i][1]); | ||
helmertCoefsMatrix.set(2 * i, 2, this.sourcePoints[i][0]); | ||
helmertCoefsMatrix.set(2 * i, 3, -this.sourcePoints[i][1]); | ||
helmertCoefsMatrix.set(2 * i + 1, 0, 0); | ||
helmertCoefsMatrix.set(2 * i + 1, 1, 1); | ||
helmertCoefsMatrix.set(2 * i + 1, 2, sourcePoints[i][1]); | ||
helmertCoefsMatrix.set(2 * i + 1, 3, sourcePoints[i][0]); | ||
helmertCoefsMatrix.set(2 * i + 1, 2, this.sourcePoints[i][1]); | ||
helmertCoefsMatrix.set(2 * i + 1, 3, this.sourcePoints[i][0]); | ||
} | ||
// Compute helmert parameters by solving the linear system of equations for each target component | ||
// Compute helmert parameters by solving the linear system of equations for each component | ||
// Will result in a Matrix([[t_x], [t_y], [m], [n]]) | ||
@@ -45,8 +44,8 @@ const pseudoInverseHelmertCoefsMatrix = pseudoInverse(helmertCoefsMatrix); | ||
} | ||
// The interpolant function will compute the value at any point. | ||
interpolate(newSourcePoint) { | ||
// Evaluate the transformation function at a new point | ||
evaluateFunction(newSourcePoint) { | ||
if (!this.helmertParameters) { | ||
throw new Error('Helmert parameters not computed'); | ||
} | ||
// Compute the interpolated value by applying the helmert coefficients to the input point | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPoint = [ | ||
@@ -69,2 +68,26 @@ this.helmertParameters[0] + | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeX(_newSourcePoint) { | ||
if (!this.helmertParameters) { | ||
throw new Error('Helmert parameters not computed'); | ||
} | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPointPartDerX = [ | ||
this.helmertParameters[2], | ||
this.helmertParameters[3] | ||
]; | ||
return newDestinationPointPartDerX; | ||
} | ||
// Evaluate the transformation function's partial derivative to y at a new point | ||
evaluatePartialDerivativeY(_newSourcePoint) { | ||
if (!this.helmertParameters) { | ||
throw new Error('Helmert parameters not computed'); | ||
} | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPointPartDerY = [ | ||
-this.helmertParameters[3], | ||
this.helmertParameters[2] | ||
]; | ||
return newDestinationPointPartDerY; | ||
} | ||
} |
@@ -1,7 +0,8 @@ | ||
export declare function linearKernel(r: number): number; | ||
export declare function cubicKernel(r: number): number; | ||
export declare function quinticKernel(r: number): number; | ||
export declare function thinPlateKernel(r: number): number; | ||
export declare function gaussianKernel(r: number, epsilon: number): number; | ||
export declare function inverseMultiquadricKernel(r: number, epsilon: number): number; | ||
export declare function multiquadricKernel(r: number, epsilon: number): number; | ||
import { KernelFunctionOptions } from './types'; | ||
export declare function linearKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function cubicKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function quinticKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function thinPlateKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function gaussianKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function inverseMultiquadricKernel(r: number, options: KernelFunctionOptions): number; | ||
export declare function multiquadricKernel(r: number, options: KernelFunctionOptions): number; |
@@ -1,24 +0,90 @@ | ||
export function linearKernel(r) { | ||
return r; | ||
export function linearKernel(r, options) { | ||
if (!options.derivative) { | ||
return r; | ||
} | ||
else if (options.derivative == 1) { | ||
return 1; | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function cubicKernel(r) { | ||
return Math.pow(r, 3); | ||
export function cubicKernel(r, options) { | ||
if (!options.derivative) { | ||
return Math.pow(r, 3); | ||
} | ||
else if (options.derivative == 1) { | ||
return 3 * Math.pow(r, 2); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function quinticKernel(r) { | ||
return Math.pow(r, 5); | ||
export function quinticKernel(r, options) { | ||
if (!options.derivative) { | ||
return Math.pow(r, 5); | ||
} | ||
else if (options.derivative == 1) { | ||
return 5 * Math.pow(r, 4); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function thinPlateKernel(r) { | ||
if (r === 0) { | ||
return 0; | ||
export function thinPlateKernel(r, options) { | ||
if (!options.derivative) { | ||
if (r === 0) { | ||
return 0; | ||
} | ||
return Math.pow(r, 2) * Math.log(r); | ||
} | ||
return Math.pow(r, 2) * Math.log(r); | ||
else if (options.derivative == 1) { | ||
if (r === 0) { | ||
return 0; | ||
} | ||
return r + 2 * r * Math.log(r); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function gaussianKernel(r, epsilon) { | ||
return Math.exp(-Math.pow(r / epsilon, 2)); | ||
export function gaussianKernel(r, options) { | ||
options.epsilon = options.epsilon || 1; | ||
if (!options.derivative) { | ||
return Math.exp(-Math.pow(r / options.epsilon, 2)); | ||
} | ||
else if (options.derivative == 1) { | ||
return (((-2 * r) / options.epsilon ** 2) * | ||
Math.exp(-Math.pow(r / options.epsilon, 2))); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function inverseMultiquadricKernel(r, epsilon) { | ||
return 1.0 / Math.sqrt(Math.pow(r / epsilon, 2) + 1); | ||
export function inverseMultiquadricKernel(r, options) { | ||
options.epsilon = options.epsilon || 1; | ||
if (!options.derivative) { | ||
return 1.0 / Math.sqrt(Math.pow(r / options.epsilon, 2) + 1); | ||
} | ||
else if (options.derivative == 1) { | ||
return (-r / | ||
(options.epsilon ** 2 * | ||
Math.pow(Math.pow(r / options.epsilon, 2) + 1, 3 / 2))); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} | ||
export function multiquadricKernel(r, epsilon) { | ||
return Math.sqrt(Math.pow(r / epsilon, 2) + 1); | ||
export function multiquadricKernel(r, options) { | ||
options.epsilon = options.epsilon || 1; | ||
if (!options.derivative) { | ||
return Math.sqrt(Math.pow(r / options.epsilon, 2) + 1); | ||
} | ||
else if (options.derivative == 1) { | ||
return (r / | ||
(options.epsilon ** 2 * Math.sqrt(Math.pow(r / options.epsilon, 2) + 1))); | ||
} | ||
else { | ||
throw new Error('Derivate of order ' + options.derivative + ' not implemented'); | ||
} | ||
} |
import type { Point } from '@allmaps/types'; | ||
export declare function euclideanNorm(point1: Point, point2: Point): number; | ||
export declare function euclideanNorm(point0: Point, point1: Point): number; |
@@ -1,5 +0,5 @@ | ||
export function euclideanNorm(point1, point2) { | ||
const sub = [point2[0] - point1[0], point2[1] - point1[1]]; | ||
export function euclideanNorm(point0, point1) { | ||
const sub = [point1[0] - point0[0], point1[1] - point0[1]]; | ||
const norm = Math.sqrt(sub[0] ** 2 + sub[1] ** 2); | ||
return norm; | ||
} |
import { Matrix } from 'ml-matrix'; | ||
import type { Transformation } from './types'; | ||
import Transformation from '../transformation.js'; | ||
import type { Point } from '@allmaps/types'; | ||
export default class Polynomial implements Transformation { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
export default class Polynomial extends Transformation { | ||
polynomialParametersMatrices: [Matrix, Matrix]; | ||
polynomialParameters: [number[], number[]]; | ||
pointCount: number; | ||
order: number; | ||
nCoefs: number; | ||
pointCountMinimum: number; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[], order?: number); | ||
interpolate(newSourcePoint: Point): Point; | ||
evaluateFunction(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeX(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeY(newSourcePoint: Point): Point; | ||
} |
import { Matrix, pseudoInverse } from 'ml-matrix'; | ||
export default class Polynomial { | ||
import Transformation from '../transformation.js'; | ||
export default class Polynomial extends Transformation { | ||
polynomialParametersMatrices; | ||
polynomialParameters; | ||
order; | ||
pointCountMinimum; | ||
constructor(sourcePoints, destinationPoints, order) { | ||
this.sourcePoints = sourcePoints; | ||
this.destinationPoints = destinationPoints; | ||
this.pointCount = this.sourcePoints.length; | ||
this.order = order || 1; | ||
this.nCoefs = ((this.order + 1) * (this.order + 2)) / 2; | ||
// if there are less control points then there are coefficients to be determined (for each dimension), the system can not be solved | ||
if (this.pointCount < this.nCoefs) { | ||
throw new Error('Not enough control points. A polynomial transformation of order ' + | ||
this.order + | ||
' requires a minimum of ' + | ||
this.nCoefs + | ||
' points, but ' + | ||
this.pointCount + | ||
' are given.'); | ||
} | ||
order = order || 1; | ||
const pointsCountMinimum = ((order + 1) * (order + 2)) / 2; | ||
// If there are less control points then there are coefficients to be determined (for each dimension), the system can not be solved | ||
super(sourcePoints, destinationPoints, ('polynomial' + order), pointsCountMinimum); | ||
this.order = order; | ||
this.pointCountMinimum = pointsCountMinimum; | ||
if (this.order < 1 || this.order > 3) { | ||
@@ -28,4 +24,4 @@ throw new Error('Only polynomial transformations of order 1, 2 or 3 are supported'); | ||
const destinationPointsMatrices = [ | ||
Matrix.columnVector(destinationPoints.map((value) => value[0])), | ||
Matrix.columnVector(destinationPoints.map((value) => value[1])) | ||
Matrix.columnVector(this.destinationPoints.map((value) => value[0])), | ||
Matrix.columnVector(this.destinationPoints.map((value) => value[1])) | ||
]; | ||
@@ -44,3 +40,3 @@ // Construct Nx3 Matrix polynomialCoefsMatrix | ||
// ... | ||
const polynomialCoefsMatrix = Matrix.zeros(this.pointCount, this.nCoefs); | ||
const polynomialCoefsMatrix = Matrix.zeros(this.pointCount, this.pointCountMinimum); | ||
for (let i = 0; i < this.pointCount; i++) { | ||
@@ -50,24 +46,24 @@ switch (this.order) { | ||
polynomialCoefsMatrix.set(i, 0, 1); | ||
polynomialCoefsMatrix.set(i, 1, sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 1, this.sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, this.sourcePoints[i][1]); | ||
break; | ||
case 2: | ||
polynomialCoefsMatrix.set(i, 0, 1); | ||
polynomialCoefsMatrix.set(i, 1, sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 3, sourcePoints[i][0] ** 2); | ||
polynomialCoefsMatrix.set(i, 4, sourcePoints[i][1] ** 2); | ||
polynomialCoefsMatrix.set(i, 5, sourcePoints[i][0] * sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 1, this.sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, this.sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 3, this.sourcePoints[i][0] ** 2); | ||
polynomialCoefsMatrix.set(i, 4, this.sourcePoints[i][1] ** 2); | ||
polynomialCoefsMatrix.set(i, 5, this.sourcePoints[i][0] * this.sourcePoints[i][1]); | ||
break; | ||
case 3: | ||
polynomialCoefsMatrix.set(i, 0, 1); | ||
polynomialCoefsMatrix.set(i, 1, sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 3, sourcePoints[i][0] ** 2); | ||
polynomialCoefsMatrix.set(i, 4, sourcePoints[i][1] ** 2); | ||
polynomialCoefsMatrix.set(i, 5, sourcePoints[i][0] * sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 6, sourcePoints[i][0] ** 3); | ||
polynomialCoefsMatrix.set(i, 7, sourcePoints[i][1] ** 3); | ||
polynomialCoefsMatrix.set(i, 8, sourcePoints[i][0] ** 2 * sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 9, sourcePoints[i][0] * sourcePoints[i][1] ** 2); | ||
polynomialCoefsMatrix.set(i, 1, this.sourcePoints[i][0]); | ||
polynomialCoefsMatrix.set(i, 2, this.sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 3, this.sourcePoints[i][0] ** 2); | ||
polynomialCoefsMatrix.set(i, 4, this.sourcePoints[i][1] ** 2); | ||
polynomialCoefsMatrix.set(i, 5, this.sourcePoints[i][0] * this.sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 6, this.sourcePoints[i][0] ** 3); | ||
polynomialCoefsMatrix.set(i, 7, this.sourcePoints[i][1] ** 3); | ||
polynomialCoefsMatrix.set(i, 8, this.sourcePoints[i][0] ** 2 * this.sourcePoints[i][1]); | ||
polynomialCoefsMatrix.set(i, 9, this.sourcePoints[i][0] * this.sourcePoints[i][1] ** 2); | ||
break; | ||
@@ -78,3 +74,3 @@ default: | ||
} | ||
// Compute polynomial parameters by solving the linear system of equations for each target component | ||
// Compute polynomial parameters by solving the linear system of equations for each component | ||
// Note: this solution uses the 'pseudo inverse' see https://en.wikipedia.org/wiki/Moore%E2%80%93Penrose_inverse | ||
@@ -92,8 +88,8 @@ // This wil result in: | ||
} | ||
// The interpolant function will compute the value at any point. | ||
interpolate(newSourcePoint) { | ||
// Evaluate the transformation function at a new point | ||
evaluateFunction(newSourcePoint) { | ||
if (!this.polynomialParameters) { | ||
throw new Error('Polynomial parameters not computed'); | ||
} | ||
// Compute the interpolated value by applying the polynomial coefficients to the input point | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPoint = [0, 0]; | ||
@@ -144,2 +140,74 @@ for (let i = 0; i < 2; i++) { | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeX(newSourcePoint) { | ||
if (!this.polynomialParameters) { | ||
throw new Error('Polynomial parameters not computed'); | ||
} | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPointPartDerX = [0, 0]; | ||
for (let i = 0; i < 2; i++) { | ||
switch (this.order) { | ||
case 1: | ||
newDestinationPointPartDerX[i] += this.polynomialParameters[i][1]; | ||
break; | ||
case 2: | ||
newDestinationPointPartDerX[i] += | ||
this.polynomialParameters[i][1] + | ||
2 * this.polynomialParameters[i][3] * newSourcePoint[0] + | ||
this.polynomialParameters[i][5] * newSourcePoint[1]; | ||
break; | ||
case 3: | ||
newDestinationPointPartDerX[i] += | ||
this.polynomialParameters[i][1] + | ||
2 * this.polynomialParameters[i][3] * newSourcePoint[0] + | ||
this.polynomialParameters[i][5] * newSourcePoint[1] + | ||
3 * this.polynomialParameters[i][6] * newSourcePoint[0] ** 2 + | ||
2 * | ||
this.polynomialParameters[i][8] * | ||
newSourcePoint[0] * | ||
newSourcePoint[1] + | ||
this.polynomialParameters[i][9] * newSourcePoint[1] ** 2; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
return newDestinationPointPartDerX; | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeY(newSourcePoint) { | ||
if (!this.polynomialParameters) { | ||
throw new Error('Polynomial parameters not computed'); | ||
} | ||
// Apply the helmert coefficients to the input point | ||
const newDestinationPointPartDerY = [0, 0]; | ||
for (let i = 0; i < 2; i++) { | ||
switch (this.order) { | ||
case 1: | ||
newDestinationPointPartDerY[i] += this.polynomialParameters[i][2]; | ||
break; | ||
case 2: | ||
newDestinationPointPartDerY[i] += | ||
this.polynomialParameters[i][2] + | ||
2 * this.polynomialParameters[i][4] * newSourcePoint[1] + | ||
this.polynomialParameters[i][5] * newSourcePoint[0]; | ||
break; | ||
case 3: | ||
newDestinationPointPartDerY[i] += | ||
this.polynomialParameters[i][2] + | ||
2 * this.polynomialParameters[i][4] * newSourcePoint[1] + | ||
this.polynomialParameters[i][5] * newSourcePoint[0] + | ||
3 * this.polynomialParameters[i][7] * newSourcePoint[1] ** 2 + | ||
this.polynomialParameters[i][8] * newSourcePoint[0] ** 2 + | ||
2 * | ||
this.polynomialParameters[i][9] * | ||
newSourcePoint[0] * | ||
newSourcePoint[1]; | ||
break; | ||
default: | ||
break; | ||
} | ||
} | ||
return newDestinationPointPartDerY; | ||
} | ||
} |
import { Matrix } from 'ml-matrix'; | ||
import type { Transformation } from './types'; | ||
import Transformation from '../transformation.js'; | ||
import type { Point } from '@allmaps/types'; | ||
export default class Projective implements Transformation { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
export default class Projective extends Transformation { | ||
projectiveParametersMatrix: Matrix; | ||
projectiveParameters: number[][]; | ||
pointCount: number; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[]); | ||
interpolate(newSourcePoint: Point): Point; | ||
evaluateFunction(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeX(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeY(newSourcePoint: Point): Point; | ||
} |
import { Matrix, SingularValueDecomposition } from 'ml-matrix'; | ||
export default class Projective { | ||
import Transformation from '../transformation.js'; | ||
export default class Projective extends Transformation { | ||
projectiveParametersMatrix; | ||
projectiveParameters; | ||
constructor(sourcePoints, destinationPoints) { | ||
this.sourcePoints = sourcePoints; | ||
this.destinationPoints = destinationPoints; | ||
this.pointCount = this.sourcePoints.length; | ||
if (this.pointCount < 4) { | ||
throw new Error('Not enough control points. A projective transformation requires a minimum of 4 points, but ' + | ||
this.pointCount + | ||
' are given.'); | ||
} | ||
super(sourcePoints, destinationPoints, 'projective', 4); | ||
// 2D projective (= perspective) transformation | ||
@@ -46,23 +42,70 @@ // See https://citeseerx.ist.psu.edu/doc/10.1.1.186.4411 for more information | ||
} | ||
// The interpolant function will compute the value at any point. | ||
interpolate(newSourcePoint) { | ||
// Evaluate the transformation function at a new point | ||
evaluateFunction(newSourcePoint) { | ||
if (!this.projectiveParameters) { | ||
throw new Error('projective parameters not computed'); | ||
} | ||
// Compute the interpolated value by applying the coefficients to the input point | ||
// Apply the coefficients to the input point | ||
const c = this.projectiveParameters[0][2] * newSourcePoint[0] + | ||
this.projectiveParameters[1][2] * newSourcePoint[1] + | ||
this.projectiveParameters[2][2]; | ||
const newDestinationPoint = [ | ||
(this.projectiveParameters[0][0] * newSourcePoint[0] + | ||
this.projectiveParameters[1][0] * newSourcePoint[1] + | ||
this.projectiveParameters[2][0]) / | ||
c, | ||
(this.projectiveParameters[0][1] * newSourcePoint[0] + | ||
this.projectiveParameters[1][1] * newSourcePoint[1] + | ||
this.projectiveParameters[2][1]) / | ||
c | ||
]; | ||
const num1 = this.projectiveParameters[0][0] * newSourcePoint[0] + | ||
this.projectiveParameters[1][0] * newSourcePoint[1] + | ||
this.projectiveParameters[2][0]; | ||
const num2 = this.projectiveParameters[0][1] * newSourcePoint[0] + | ||
this.projectiveParameters[1][1] * newSourcePoint[1] + | ||
this.projectiveParameters[2][1]; | ||
const newDestinationPoint = [num1 / c, num2 / c]; | ||
return newDestinationPoint; | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeX(newSourcePoint) { | ||
if (!this.projectiveParameters) { | ||
throw new Error('projective parameters not computed'); | ||
} | ||
// Apply the coefficients to the input point | ||
const c = this.projectiveParameters[0][2] * newSourcePoint[0] + | ||
this.projectiveParameters[1][2] * newSourcePoint[1] + | ||
this.projectiveParameters[2][2]; | ||
const num1 = this.projectiveParameters[0][0] * newSourcePoint[0] + | ||
this.projectiveParameters[1][0] * newSourcePoint[1] + | ||
this.projectiveParameters[2][0]; | ||
const num2 = this.projectiveParameters[0][1] * newSourcePoint[0] + | ||
this.projectiveParameters[1][1] * newSourcePoint[1] + | ||
this.projectiveParameters[2][1]; | ||
const newDestinationPointPartDerX = [ | ||
(c * this.projectiveParameters[0][0] - | ||
this.projectiveParameters[0][2] * num1) / | ||
c ** 2, | ||
(c * this.projectiveParameters[0][1] - | ||
this.projectiveParameters[0][2] * num2) / | ||
c ** 2 | ||
]; | ||
return newDestinationPointPartDerX; | ||
} | ||
// Evaluate the transformation function's partial derivative to y at a new point | ||
evaluatePartialDerivativeY(newSourcePoint) { | ||
if (!this.projectiveParameters) { | ||
throw new Error('projective parameters not computed'); | ||
} | ||
// Apply the coefficients to the input point | ||
const c = this.projectiveParameters[0][2] * newSourcePoint[0] + | ||
this.projectiveParameters[1][2] * newSourcePoint[1] + | ||
this.projectiveParameters[2][2]; | ||
const num1 = this.projectiveParameters[0][0] * newSourcePoint[0] + | ||
this.projectiveParameters[1][0] * newSourcePoint[1] + | ||
this.projectiveParameters[2][0]; | ||
const num2 = this.projectiveParameters[0][1] * newSourcePoint[0] + | ||
this.projectiveParameters[1][1] * newSourcePoint[1] + | ||
this.projectiveParameters[2][1]; | ||
const newDestinationPointPartDerY = [ | ||
(c * this.projectiveParameters[1][0] - | ||
this.projectiveParameters[1][2] * num1) / | ||
c ** 2, | ||
(c * this.projectiveParameters[1][1] - | ||
this.projectiveParameters[1][2] * num2) / | ||
c ** 2 | ||
]; | ||
return newDestinationPointPartDerY; | ||
} | ||
} |
import { Matrix } from 'ml-matrix'; | ||
import type { Transformation } from './types'; | ||
import Transformation from '../transformation.js'; | ||
import type { KernelFunction, NormFunction } from './types.js'; | ||
import type { Point } from '@allmaps/types'; | ||
export default class RBF implements Transformation { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
export default class RBF extends Transformation { | ||
kernelFunction: KernelFunction; | ||
@@ -13,6 +11,7 @@ normFunction: NormFunction; | ||
affineWeights: [number[], number[]]; | ||
pointCount: number; | ||
epsilon?: number; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[], kernelFunction: KernelFunction, normFunction: NormFunction, epsilon?: number); | ||
interpolate(newSourcePoint: Point): Point; | ||
evaluateFunction(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeX(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeY(newSourcePoint: Point): Point; | ||
} |
import { Matrix, inverse } from 'ml-matrix'; | ||
export default class RBF { | ||
import Transformation from '../transformation.js'; | ||
export default class RBF extends Transformation { | ||
kernelFunction; | ||
normFunction; | ||
weightsMatrices; | ||
rbfWeights; | ||
affineWeights; | ||
epsilon; | ||
constructor(sourcePoints, destinationPoints, kernelFunction, normFunction, epsilon) { | ||
this.sourcePoints = sourcePoints; | ||
this.destinationPoints = destinationPoints; | ||
super(sourcePoints, destinationPoints, 'thinPlateSpline', 3); | ||
this.kernelFunction = kernelFunction; | ||
this.normFunction = normFunction; | ||
this.pointCount = this.sourcePoints.length; | ||
if (this.pointCount < 3) { | ||
throw new Error(`Not enough control points. A thin plate spline transformation (with affine component) requires a minimum of 3 points, but ${this.pointCount} are given.`); | ||
} | ||
// 2D Radial Basis Function interpolation | ||
@@ -18,4 +20,4 @@ // See notebook https://observablehq.com/d/0b57d3b587542794 for code source and explanation | ||
const destinationPointsMatrices = [ | ||
Matrix.columnVector([...destinationPoints, [0, 0], [0, 0], [0, 0]].map((value) => value[0])), | ||
Matrix.columnVector([...destinationPoints, [0, 0], [0, 0], [0, 0]].map((value) => value[1])) | ||
Matrix.columnVector([...this.destinationPoints, [0, 0], [0, 0], [0, 0]].map((value) => value[0])), | ||
Matrix.columnVector([...this.destinationPoints, [0, 0], [0, 0], [0, 0]].map((value) => value[1])) | ||
]; | ||
@@ -26,3 +28,3 @@ // Pre-compute kernelsMatrix: fill it with the point to point distances between all control points | ||
for (let j = 0; j < this.pointCount; j++) { | ||
kernelsMatrix.set(i, j, normFunction(sourcePoints[i], sourcePoints[j])); | ||
kernelsMatrix.set(i, j, normFunction(this.sourcePoints[i], this.sourcePoints[j])); | ||
} | ||
@@ -40,3 +42,3 @@ } | ||
for (let j = 0; j < this.pointCount; j++) { | ||
kernelsMatrix.set(i, j, kernelFunction(kernelsMatrix.get(i, j), epsilon)); | ||
kernelsMatrix.set(i, j, kernelFunction(kernelsMatrix.get(i, j), { epsilon: epsilon })); | ||
} | ||
@@ -54,4 +56,4 @@ } | ||
affineCoefsMatrix.set(i, 0, 1); | ||
affineCoefsMatrix.set(i, 1, sourcePoints[i][0]); | ||
affineCoefsMatrix.set(i, 2, sourcePoints[i][1]); | ||
affineCoefsMatrix.set(i, 1, this.sourcePoints[i][0]); | ||
affineCoefsMatrix.set(i, 2, this.sourcePoints[i][1]); | ||
} | ||
@@ -74,3 +76,3 @@ // Combine kernelsMatrix and affineCoefsMatrix into new matrix kernelsAndAffineCoefsMatrix | ||
} | ||
// Compute basis functions weights and the affine parameters by solving the linear system of equations for each target component | ||
// Compute basis functions weights and the affine parameters by solving the linear system of equations for each component | ||
// Note: the same kernelsAndAffineCoefsMatrix is used for both solutions | ||
@@ -82,3 +84,3 @@ const inverseKernelsAndAffineCoefsMatrix = inverse(kernelsAndAffineCoefsMatrix); | ||
]; | ||
// Store rbf and affine parts of the weights more as arrays for more efficient access on interpolation | ||
// Store rbf and affine parts of the weights more as arrays for more efficient access on evaluation | ||
this.rbfWeights = this.weightsMatrices.map((matrix) => matrix.selection([...Array(this.pointCount).keys()], [0]).to1DArray()); | ||
@@ -89,14 +91,16 @@ this.affineWeights = this.weightsMatrices.map((matrix) => matrix | ||
} | ||
// The interpolant function will compute the value at any point. | ||
interpolate(newSourcePoint) { | ||
// Evaluate the transformation function at a new point | ||
evaluateFunction(newSourcePoint) { | ||
if (!this.rbfWeights || !this.affineWeights) { | ||
throw new Error('Weights not computed'); | ||
} | ||
// Make a column matrix with the distances of that point to all control points | ||
const newDistances = this.sourcePoints.map((sourcePoint) => this.kernelFunction(this.normFunction(newSourcePoint, sourcePoint), this.epsilon)); | ||
// Compute the interpolated value by summing the weighted contributions of the input point | ||
// Compute the distances of that point to all control points | ||
const newDistances = this.sourcePoints.map((sourcePoint) => this.normFunction(newSourcePoint, sourcePoint)); | ||
// Sum the weighted contributions of the input point | ||
const newDestinationPoint = [0, 0]; | ||
for (let i = 0; i < 2; i++) { | ||
// Apply the weights to the new distances | ||
newDestinationPoint[i] = newDistances.reduce((r0, e0, i0) => r0 + e0 * this.rbfWeights[i][i0], 0); | ||
newDestinationPoint[i] = newDistances.reduce((sum, dist, index) => sum + | ||
this.kernelFunction(dist, { epsilon: this.epsilon }) * | ||
this.rbfWeights[i][index], 0); | ||
// Add the affine part | ||
@@ -110,2 +114,52 @@ newDestinationPoint[i] += | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeX(newSourcePoint) { | ||
if (!this.rbfWeights || !this.affineWeights) { | ||
throw new Error('Weights not computed'); | ||
} | ||
// Compute the distances of that point to all control points | ||
const newDistances = this.sourcePoints.map((sourcePoint) => this.normFunction(newSourcePoint, sourcePoint)); | ||
// Sum the weighted contributions of the input point | ||
const newDestinationPointPartDerX = [0, 0]; | ||
for (let i = 0; i < 2; i++) { | ||
// Apply the weights to the new distances | ||
newDestinationPointPartDerX[i] = newDistances.reduce((sum, dist, index) => sum + | ||
(dist == 0 | ||
? 0 | ||
: this.kernelFunction(dist, { | ||
derivative: 1, | ||
epsilon: this.epsilon | ||
}) * | ||
((newSourcePoint[0] - this.sourcePoints[index][0]) / dist) * | ||
this.rbfWeights[i][index]), 0); | ||
// Add the affine part | ||
newDestinationPointPartDerX[i] += this.affineWeights[i][1]; | ||
} | ||
return newDestinationPointPartDerX; | ||
} | ||
// Evaluate the transformation function's partial derivative to y at a new point | ||
evaluatePartialDerivativeY(newSourcePoint) { | ||
if (!this.rbfWeights || !this.affineWeights) { | ||
throw new Error('Weights not computed'); | ||
} | ||
// Compute the distances of that point to all control points | ||
const newDistances = this.sourcePoints.map((sourcePoint) => this.normFunction(newSourcePoint, sourcePoint)); | ||
// Sum the weighted contributions of the input point | ||
const newDestinationPointPartDerY = [0, 0]; | ||
for (let i = 0; i < 2; i++) { | ||
// Apply the weights to the new distances | ||
newDestinationPointPartDerY[i] = newDistances.reduce((sum, dist, index) => sum + | ||
(dist == 0 | ||
? 0 | ||
: this.kernelFunction(dist, { | ||
derivative: 1, | ||
epsilon: this.epsilon | ||
}) * | ||
((newSourcePoint[1] - this.sourcePoints[index][1]) / dist) * | ||
this.rbfWeights[i][index]), 0); | ||
// Add the affine part | ||
newDestinationPointPartDerY[i] += this.affineWeights[i][2]; | ||
} | ||
return newDestinationPointPartDerY; | ||
} | ||
} |
@@ -1,7 +0,4 @@ | ||
import type { PartialTransformOptions, Transformation } from './types'; | ||
import Transformation from '../transformation.js'; | ||
import type { Point } from '@allmaps/types'; | ||
export default class Straight implements Transformation { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
options?: PartialTransformOptions; | ||
export default class Straight extends Transformation { | ||
scale?: number; | ||
@@ -11,5 +8,6 @@ sourcePointsCenter: Point; | ||
translation?: Point; | ||
pointCount: number; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[], options?: PartialTransformOptions); | ||
interpolate(newSourcePoint: Point): Point; | ||
constructor(sourcePoints: Point[], destinationPoints: Point[]); | ||
evaluateFunction(newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeX(_newSourcePoint: Point): Point; | ||
evaluatePartialDerivativeY(_newSourcePoint: Point): Point; | ||
} |
import Helmert from './helmert.js'; | ||
export default class Straight { | ||
constructor(sourcePoints, destinationPoints, options) { | ||
this.sourcePoints = sourcePoints; | ||
this.destinationPoints = destinationPoints; | ||
this.options = options; | ||
this.pointCount = this.sourcePoints.length; | ||
if (this.pointCount < 2) { | ||
throw new Error('Not enough control points. A straight transformation requires a minimum of 2 points, but ' + | ||
this.pointCount + | ||
' are given.'); | ||
} | ||
import Transformation from '../transformation.js'; | ||
export default class Straight extends Transformation { | ||
scale; | ||
sourcePointsCenter; | ||
destinationPointsCenter; | ||
translation; | ||
constructor(sourcePoints, destinationPoints) { | ||
super(sourcePoints, destinationPoints, 'straight', 2); | ||
// Compute the corrensponing Helmert transform and get the scale from it | ||
const helmertTransformation = new Helmert(sourcePoints, destinationPoints); | ||
const helmertTransformation = new Helmert(this.sourcePoints, this.destinationPoints); | ||
this.scale = helmertTransformation.scale; | ||
@@ -30,4 +27,4 @@ if (!this.scale) { | ||
} | ||
// The interpolant function will compute the value at any point. | ||
interpolate(newSourcePoint) { | ||
// Evaluate the transformation function at a new point | ||
evaluateFunction(newSourcePoint) { | ||
if (!this.scale || !this.translation) { | ||
@@ -42,2 +39,18 @@ throw new Error('Straight parameters not computed'); | ||
} | ||
// Evaluate the transformation function's partial derivative to x at a new point | ||
evaluatePartialDerivativeX(_newSourcePoint) { | ||
if (!this.scale || !this.translation) { | ||
throw new Error('Straight parameters not computed'); | ||
} | ||
const newDestinationPointPartDerX = [this.scale, 0]; | ||
return newDestinationPointPartDerX; | ||
} | ||
// Evaluate the transformation function's partial derivative to y at a new point | ||
evaluatePartialDerivativeY(_newSourcePoint) { | ||
if (!this.scale || !this.translation) { | ||
throw new Error('Straight parameters not computed'); | ||
} | ||
const newDestinationPointPartDerY = [0, this.scale]; | ||
return newDestinationPointPartDerY; | ||
} | ||
} |
import GcpTransformer from '../transformer'; | ||
import type { TransformOptions, PartialTransformOptions } from './types.js'; | ||
import type { TransformOptions } from './types.js'; | ||
import type { LineString, Ring, Polygon } from '@allmaps/types'; | ||
export declare function mergeOptions(optionsFromTransform?: PartialTransformOptions, optionsFromGCPTransformer?: PartialTransformOptions, optionsFromDataFormat?: PartialTransformOptions): TransformOptions; | ||
export declare function mergeOptions(optionsFromTransform?: Partial<TransformOptions>, optionsFromGCPTransformer?: Partial<TransformOptions>, optionsFromDataFormat?: Partial<TransformOptions>): TransformOptions; | ||
export declare function transformLineStringForwardToLineString(transformer: GcpTransformer, lineString: LineString, options: TransformOptions): LineString; | ||
@@ -6,0 +6,0 @@ export declare function transformLineStringBackwardToLineString(transformer: GcpTransformer, lineString: LineString, options: TransformOptions): LineString; |
@@ -12,3 +12,4 @@ // TODO: consider implementing these functions in this module instead of using dependencies | ||
inputIsMultiGeometry: false, | ||
differentHandedness: false | ||
differentHandedness: false, | ||
evaluationType: 'function' | ||
}; | ||
@@ -15,0 +16,0 @@ return { |
@@ -23,12 +23,11 @@ import type { Point } from '@allmaps/types'; | ||
differentHandedness: boolean; | ||
evaluationType: EvaluationType; | ||
}; | ||
export type PartialTransformOptions = Partial<TransformOptions>; | ||
export type KernelFunction = (r: number, epsilon?: number) => number; | ||
export type NormFunction = (point1: Point, point2: Point) => number; | ||
export type Transformation = { | ||
sourcePoints: Point[]; | ||
destinationPoints: Point[]; | ||
options?: PartialTransformOptions; | ||
pointCount: number; | ||
interpolate(point: Point): Point; | ||
export type KernelFunction = (r: number, options: KernelFunctionOptions) => number; | ||
export type KernelFunctionOptions = { | ||
derivative?: number; | ||
epsilon?: number; | ||
}; | ||
export type NormFunction = (point0: Point, point1: Point) => number; | ||
export type EvaluationType = 'function' | 'partialDerivativeX' | 'partialDerivativeY'; | ||
export type DistortionMeasure = 'log2sigma' | 'twoOmega' | 'airyKavr' | 'signDetJ' | 'thetaa'; |
@@ -1,3 +0,4 @@ | ||
import type { Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, Gcp, GeojsonPoint, GeojsonLineString, GeojsonPolygon, GeojsonMultiPoint, GeojsonMultiLineString, GeojsonMultiPolygon, GeojsonGeometry, SvgGeometry } from '@allmaps/types'; | ||
import type { TransformGcp, TransformationType, PartialTransformOptions, Transformation } from './shared/types.js'; | ||
import Transformation from './transformation.js'; | ||
import type { Point, LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, Gcp, GeojsonPoint, GeojsonLineString, GeojsonPolygon, GeojsonMultiPoint, GeojsonMultiLineString, GeojsonMultiPolygon, GeojsonGeometry, GeojsonFeatureCollection, SvgGeometry } from '@allmaps/types'; | ||
import type { TransformGcp, TransformationType, TransformOptions } from './shared/types.js'; | ||
/** | ||
@@ -12,3 +13,3 @@ * A Ground Control Point Transformer, containing a forward and backward transformation and | ||
type: TransformationType; | ||
options?: PartialTransformOptions; | ||
options?: Partial<TransformOptions>; | ||
forwardTransformation?: Transformation; | ||
@@ -20,56 +21,60 @@ backwardTransformation?: Transformation; | ||
* @param {TransformationType} [type='polynomial'] - The transformation type | ||
*/ constructor(gcps: TransformGcp[] | Gcp[], type?: TransformationType, options?: PartialTransformOptions); | ||
private assureEqualHandedness; | ||
private createForwardTransformation; | ||
private createBackwardTransformation; | ||
private createTransformation; | ||
transformForward(input: Point | GeojsonPoint, options?: PartialTransformOptions): Point; | ||
transformForward(input: LineString | GeojsonLineString, options?: PartialTransformOptions): LineString; | ||
transformForward(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): Polygon; | ||
transformForward(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): MultiPoint; | ||
transformForward(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): MultiLineString; | ||
transformForward(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): MultiPolygon; | ||
transformForwardAsGeojson(input: Point | GeojsonPoint, options?: PartialTransformOptions): GeojsonPoint; | ||
transformForwardAsGeojson(input: LineString | GeojsonLineString, options?: PartialTransformOptions): GeojsonLineString; | ||
transformForwardAsGeojson(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): GeojsonPolygon; | ||
transformForwardAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): GeojsonMultiPoint; | ||
transformForwardAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): GeojsonMultiLineString; | ||
transformForwardAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): GeojsonMultiPolygon; | ||
transformBackward(input: Point | GeojsonPoint, options?: PartialTransformOptions): Point; | ||
transformBackward(input: LineString | GeojsonLineString, options?: PartialTransformOptions): LineString; | ||
transformBackward(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): Polygon; | ||
transformBackward(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): MultiPoint; | ||
transformBackward(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): MultiLineString; | ||
transformBackward(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): MultiPolygon; | ||
transformBackwardAsGeojson(input: Point | GeojsonPoint, options?: PartialTransformOptions): GeojsonPoint; | ||
transformBackwardAsGeojson(input: LineString | GeojsonLineString, options?: PartialTransformOptions): GeojsonLineString; | ||
transformBackwardAsGeojson(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): GeojsonPolygon; | ||
transformBackwardAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): GeojsonMultiPoint; | ||
transformBackwardAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): GeojsonMultiLineString; | ||
transformBackwardAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): GeojsonMultiPolygon; | ||
transformToGeo(input: Point | GeojsonPoint, options?: PartialTransformOptions): Point; | ||
transformToGeo(input: LineString | GeojsonLineString, options?: PartialTransformOptions): LineString; | ||
transformToGeo(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): Polygon; | ||
transformToGeo(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): MultiPoint; | ||
transformToGeo(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): MultiLineString; | ||
transformToGeo(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): MultiPolygon; | ||
transformToGeoAsGeojson(input: Point | GeojsonPoint, options?: PartialTransformOptions): GeojsonPoint; | ||
transformToGeoAsGeojson(input: LineString | GeojsonLineString, options?: PartialTransformOptions): GeojsonLineString; | ||
transformToGeoAsGeojson(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): GeojsonPolygon; | ||
transformToGeoAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): GeojsonMultiPoint; | ||
transformToGeoAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): GeojsonMultiLineString; | ||
transformToGeoAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): GeojsonMultiPolygon; | ||
transformToResource(input: Point | GeojsonPoint, options?: PartialTransformOptions): Point; | ||
transformToResource(input: LineString | GeojsonLineString, options?: PartialTransformOptions): LineString; | ||
transformToResource(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): Polygon; | ||
transformToResource(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): MultiPoint; | ||
transformToResource(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): MultiLineString; | ||
transformToResource(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): MultiPolygon; | ||
transformToResourceAsGeojson(input: Point | GeojsonPoint, options?: PartialTransformOptions): GeojsonPoint; | ||
transformToResourceAsGeojson(input: LineString | GeojsonLineString, options?: PartialTransformOptions): GeojsonLineString; | ||
transformToResourceAsGeojson(input: Polygon | GeojsonPolygon, options?: PartialTransformOptions): GeojsonPolygon; | ||
transformToResourceAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: PartialTransformOptions): GeojsonMultiPoint; | ||
transformToResourceAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: PartialTransformOptions): GeojsonMultiLineString; | ||
transformToResourceAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: PartialTransformOptions): GeojsonMultiPolygon; | ||
*/ constructor(gcps: TransformGcp[] | Gcp[], type?: TransformationType, options?: Partial<TransformOptions>); | ||
/** | ||
* Create forward transformation | ||
*/ | ||
createForwardTransformation(): void; | ||
/** | ||
* Create backward transformation | ||
*/ | ||
createBackwardTransformation(): void; | ||
transformForward(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): Point; | ||
transformForward(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): LineString; | ||
transformForward(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): Polygon; | ||
transformForward(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): MultiPoint; | ||
transformForward(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): MultiLineString; | ||
transformForward(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): MultiPolygon; | ||
transformForwardAsGeojson(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): GeojsonPoint; | ||
transformForwardAsGeojson(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): GeojsonLineString; | ||
transformForwardAsGeojson(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): GeojsonPolygon; | ||
transformForwardAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): GeojsonMultiPoint; | ||
transformForwardAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): GeojsonMultiLineString; | ||
transformForwardAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): GeojsonMultiPolygon; | ||
transformBackward(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): Point; | ||
transformBackward(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): LineString; | ||
transformBackward(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): Polygon; | ||
transformBackward(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): MultiPoint; | ||
transformBackward(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): MultiLineString; | ||
transformBackward(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): MultiPolygon; | ||
transformBackwardAsGeojson(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): GeojsonPoint; | ||
transformBackwardAsGeojson(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): GeojsonLineString; | ||
transformBackwardAsGeojson(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): GeojsonPolygon; | ||
transformBackwardAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): GeojsonMultiPoint; | ||
transformBackwardAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): GeojsonMultiLineString; | ||
transformBackwardAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): GeojsonMultiPolygon; | ||
transformToGeo(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): Point; | ||
transformToGeo(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): LineString; | ||
transformToGeo(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): Polygon; | ||
transformToGeo(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): MultiPoint; | ||
transformToGeo(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): MultiLineString; | ||
transformToGeo(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): MultiPolygon; | ||
transformToGeoAsGeojson(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): GeojsonPoint; | ||
transformToGeoAsGeojson(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): GeojsonLineString; | ||
transformToGeoAsGeojson(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): GeojsonPolygon; | ||
transformToGeoAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): GeojsonMultiPoint; | ||
transformToGeoAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): GeojsonMultiLineString; | ||
transformToGeoAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): GeojsonMultiPolygon; | ||
transformToResource(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): Point; | ||
transformToResource(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): LineString; | ||
transformToResource(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): Polygon; | ||
transformToResource(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): MultiPoint; | ||
transformToResource(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): MultiLineString; | ||
transformToResource(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): MultiPolygon; | ||
transformToResourceAsGeojson(input: Point | GeojsonPoint, options?: Partial<TransformOptions>): GeojsonPoint; | ||
transformToResourceAsGeojson(input: LineString | GeojsonLineString, options?: Partial<TransformOptions>): GeojsonLineString; | ||
transformToResourceAsGeojson(input: Polygon | GeojsonPolygon, options?: Partial<TransformOptions>): GeojsonPolygon; | ||
transformToResourceAsGeojson(input: MultiPoint | GeojsonMultiPoint, options?: Partial<TransformOptions>): GeojsonMultiPoint; | ||
transformToResourceAsGeojson(input: MultiLineString | GeojsonMultiLineString, options?: Partial<TransformOptions>): GeojsonMultiLineString; | ||
transformToResourceAsGeojson(input: MultiPolygon | GeojsonMultiPolygon, options?: Partial<TransformOptions>): GeojsonMultiPolygon; | ||
/** | ||
* Transforms a SVG geometry forward to a GeoJSON geometry | ||
@@ -79,6 +84,16 @@ * | ||
* @param {SvgGeometry} geometry - SVG geometry to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonGeometry} Forward transform of input, as a GeoJSON geometry | ||
*/ | ||
transformSvgToGeojson(geometry: SvgGeometry, transformOptions?: PartialTransformOptions): GeojsonGeometry; | ||
transformSvgToGeojson(geometry: SvgGeometry, options?: Partial<TransformOptions>): GeojsonGeometry; | ||
/** | ||
* Transforms a SVG string forward to a GeoJSON FeatureCollection | ||
* | ||
* Note: Multi-geometries are not supported | ||
* @param {string} svg - SVG string to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonFeatureCollection} Forward transform of input, as a GeoJSON FeatureCollection | ||
*/ | ||
transformSvgStringToGeojsonFeatureCollection(svg: string, options?: Partial<TransformOptions>): GeojsonFeatureCollection; | ||
/** | ||
* Transforms a GeoJSON geometry backward to a SVG geometry | ||
@@ -88,5 +103,17 @@ * | ||
* @param {GeojsonGeometry} geometry - GeoJSON geometry to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {SvgGeometry} Backward transform of input, as SVG geometry | ||
*/ | ||
transformGeojsonToSvg(geometry: GeojsonGeometry, transformOptions?: PartialTransformOptions): SvgGeometry; | ||
transformGeojsonToSvg(geometry: GeojsonGeometry, options?: Partial<TransformOptions>): SvgGeometry; | ||
/** | ||
* Transforms a GeoJSON FeatureCollection backward to a SVG string | ||
* | ||
* Note: Multi-geometries are not supported | ||
* @param {GeojsonFeatureCollection} geojson - GeoJSON FeatureCollection to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {string} Backward transform of input, as SVG string | ||
*/ | ||
transformGeojsonFeatureCollectionToSvgString(geojson: GeojsonFeatureCollection, options?: Partial<TransformOptions>): string; | ||
private assureEqualHandedness; | ||
private computeTransformation; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { isPoint, isLineString, isPolygon, isMultiPoint, isMultiLineString, isMultiPolygon, isGeojsonPoint, isGeojsonLineString, isGeojsonPolygon, isGeojsonMultiPoint, isGeojsonMultiLineString, isGeojsonMultiPolygon, convertPointToGeojsonPoint, convertLineStringToGeojsonLineString, convertPolygonToGeojsonPolygon, convertGeojsonPointToPoint, convertGeojsonLineStringToLineString, convertGeojsonPolygonToPolygon, expandGeojsonMultiPointToGeojsonPointArray, expandGeojsonMultiLineStringToGeojsonLineStringArray, expandGeojsonMultiPolygonToGeojsonPolygonArray, joinGeojsonPointArrayToGeojsonMultiPoint, joinGeojsonLineStringArrayToGeojsonMultiLineString, joinGeojsonPolygonArrayToGeojsonMultiPolygon, flipY } from '@allmaps/stdlib'; | ||
import { isPoint, isLineString, isPolygon, isMultiPoint, isMultiLineString, isMultiPolygon, isGeojsonPoint, isGeojsonLineString, isGeojsonPolygon, isGeojsonMultiPoint, isGeojsonMultiLineString, isGeojsonMultiPolygon, convertPointToGeojsonPoint, convertLineStringToGeojsonLineString, convertPolygonToGeojsonPolygon, convertGeojsonPointToPoint, convertGeojsonLineStringToLineString, convertGeojsonPolygonToPolygon, geometriesToFeatureCollection, featureCollectionToGeometries, stringToSvgGeometriesGenerator, svgGeometriesToSvgString, expandGeojsonMultiPointToGeojsonPointArray, expandGeojsonMultiLineStringToGeojsonLineStringArray, expandGeojsonMultiPolygonToGeojsonPolygonArray, joinGeojsonPointArrayToGeojsonMultiPoint, joinGeojsonLineStringArrayToGeojsonMultiLineString, joinGeojsonPolygonArrayToGeojsonMultiPolygon, flipY } from '@allmaps/stdlib'; | ||
import Straight from './shared/straight.js'; | ||
@@ -15,2 +15,9 @@ import Helmert from './shared/helmert.js'; | ||
export default class GcpTransformer { | ||
gcps; | ||
sourcePoints; | ||
destinationPoints; | ||
type; | ||
options; | ||
forwardTransformation; | ||
backwardTransformation; | ||
/** | ||
@@ -24,4 +31,4 @@ * Create a GcpTransformer | ||
} | ||
if (gcps.length == 0) { | ||
throw new Error('No control points.'); | ||
if (gcps.length === 0) { | ||
throw new Error('No control points'); | ||
} | ||
@@ -46,41 +53,18 @@ this.gcps = gcps.map((gcp) => { | ||
} | ||
assureEqualHandedness(point) { | ||
return this.options?.differentHandedness ? flipY(point) : point; | ||
} | ||
/** | ||
* Create forward transformation | ||
*/ | ||
createForwardTransformation() { | ||
return this.createTransformation(this.sourcePoints.map((point) => this.assureEqualHandedness(point)), this.destinationPoints); | ||
this.forwardTransformation = this.computeTransformation(this.sourcePoints.map((point) => this.assureEqualHandedness(point)), this.destinationPoints); | ||
} | ||
/** | ||
* Create backward transformation | ||
*/ | ||
createBackwardTransformation() { | ||
return this.createTransformation(this.destinationPoints, this.sourcePoints.map((point) => this.assureEqualHandedness(point))); | ||
this.backwardTransformation = this.computeTransformation(this.destinationPoints, this.sourcePoints.map((point) => this.assureEqualHandedness(point))); | ||
} | ||
createTransformation(sourcePoints, destinationPoints) { | ||
if (this.type === 'straight') { | ||
return new Straight(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'helmert') { | ||
return new Helmert(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'polynomial1' || this.type === 'polynomial') { | ||
return new Polynomial(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'polynomial2') { | ||
return new Polynomial(sourcePoints, destinationPoints, 2); | ||
} | ||
else if (this.type === 'polynomial3') { | ||
return new Polynomial(sourcePoints, destinationPoints, 3); | ||
} | ||
else if (this.type === 'projective') { | ||
return new Projective(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'thinPlateSpline') { | ||
return new RBF(sourcePoints, destinationPoints, thinPlateKernel, euclideanNorm); | ||
} | ||
else { | ||
throw new Error(`Unsupported transformation type: ${this.type}`); | ||
} | ||
} | ||
/** | ||
* Transforms a Geometry or a GeoJSON geometry forward to a Geometry | ||
* @param {Geometry | GeojsonGeometry} input - Geometry or GeoJSON geometry to transform | ||
* @param {PartialTransformOptions} [options] - Transform options | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {Geometry} Forward transform of input as Geometry | ||
@@ -100,5 +84,5 @@ * @type {{ | ||
if (!this.forwardTransformation) { | ||
this.forwardTransformation = this.createForwardTransformation(); | ||
this.createForwardTransformation(); | ||
} | ||
return this.forwardTransformation.interpolate(this.assureEqualHandedness(input)); | ||
return this.forwardTransformation.evaluate(this.assureEqualHandedness(input), mergeOptions(options, this.options).evaluationType); | ||
} | ||
@@ -151,3 +135,3 @@ else if (isGeojsonPoint(input)) { | ||
* @param {Geometry | GeojsonGeometry} input - Geometry or GeoJSON geometry to transform | ||
* @param {PartialTransformOptions} [options] - Transform options | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonGeometry} Forward transform of input, as GeoJSON geometry | ||
@@ -222,3 +206,3 @@ * @type {{ | ||
* @param {Geometry | GeojsonGeometry} input - Geometry or GeoJSON geometry to transform | ||
* @param {PartialTransformOptions} [options] - Transform options | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {Geometry} backward transform of input, as geometry | ||
@@ -238,5 +222,5 @@ * @type {{ | ||
if (!this.backwardTransformation) { | ||
this.backwardTransformation = this.createBackwardTransformation(); | ||
this.createBackwardTransformation(); | ||
} | ||
return this.assureEqualHandedness(this.backwardTransformation.interpolate(input)); | ||
return this.assureEqualHandedness(this.backwardTransformation.evaluate(input)); | ||
} | ||
@@ -291,3 +275,3 @@ else if (isGeojsonPoint(input)) { | ||
* @param {Geometry | GeojsonGeometry} input - Geometry or GeoJSON geometry to transform | ||
* @param {PartialTransformOptions} [options] - Transform options | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonGeometry} backward transform of input, as GeoJSON geometry | ||
@@ -362,2 +346,3 @@ * @type {{ | ||
* @param {Geometry | GeojsonGeometry} input - Input to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {Geometry} Forward transform of input, as Geometry | ||
@@ -422,2 +407,3 @@ * @type {{ | ||
* @param {Geometry | GeojsonGeometry} input - Input to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {Geometry} Forward transform of input, as GeoJSON geometry | ||
@@ -482,2 +468,3 @@ * @type {{ | ||
* @param {Geometry | GeojsonGeometry} input - Input to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {Geometry} Backward transform of input, as a Geometry | ||
@@ -542,2 +529,3 @@ * @type {{ | ||
* @param {Geometry | GeojsonGeometry} input - Input to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonGeometry} Backward transform of input, as a GeoJSON geometry | ||
@@ -605,5 +593,6 @@ * @type {{ | ||
* @param {SvgGeometry} geometry - SVG geometry to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonGeometry} Forward transform of input, as a GeoJSON geometry | ||
*/ | ||
transformSvgToGeojson(geometry, transformOptions) { | ||
transformSvgToGeojson(geometry, options) { | ||
if (geometry.type === 'circle') { | ||
@@ -613,12 +602,12 @@ return this.transformForwardAsGeojson(geometry.coordinates); | ||
else if (geometry.type === 'line') { | ||
return this.transformForwardAsGeojson(geometry.coordinates, transformOptions); | ||
return this.transformForwardAsGeojson(geometry.coordinates, options); | ||
} | ||
else if (geometry.type === 'polyline') { | ||
return this.transformForwardAsGeojson(geometry.coordinates, transformOptions); | ||
return this.transformForwardAsGeojson(geometry.coordinates, options); | ||
} | ||
else if (geometry.type === 'rect') { | ||
return this.transformForwardAsGeojson([geometry.coordinates], transformOptions); | ||
return this.transformForwardAsGeojson([geometry.coordinates], options); | ||
} | ||
else if (geometry.type === 'polygon') { | ||
return this.transformForwardAsGeojson([geometry.coordinates], transformOptions); | ||
return this.transformForwardAsGeojson([geometry.coordinates], options); | ||
} | ||
@@ -630,2 +619,18 @@ else { | ||
/** | ||
* Transforms a SVG string forward to a GeoJSON FeatureCollection | ||
* | ||
* Note: Multi-geometries are not supported | ||
* @param {string} svg - SVG string to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {GeojsonFeatureCollection} Forward transform of input, as a GeoJSON FeatureCollection | ||
*/ | ||
transformSvgStringToGeojsonFeatureCollection(svg, options) { | ||
const geojsonGeometries = []; | ||
for (const svgGeometry of stringToSvgGeometriesGenerator(svg)) { | ||
const geojsonGeometry = this.transformSvgToGeojson(svgGeometry, options); | ||
geojsonGeometries.push(geojsonGeometry); | ||
} | ||
return geometriesToFeatureCollection(geojsonGeometries); | ||
} | ||
/** | ||
* Transforms a GeoJSON geometry backward to a SVG geometry | ||
@@ -635,5 +640,6 @@ * | ||
* @param {GeojsonGeometry} geometry - GeoJSON geometry to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {SvgGeometry} Backward transform of input, as SVG geometry | ||
*/ | ||
transformGeojsonToSvg(geometry, transformOptions) { | ||
transformGeojsonToSvg(geometry, options) { | ||
if (geometry.type === 'Point') { | ||
@@ -648,3 +654,3 @@ return { | ||
type: 'polyline', | ||
coordinates: this.transformBackward(geometry, transformOptions) | ||
coordinates: this.transformBackward(geometry, options) | ||
}; | ||
@@ -655,3 +661,3 @@ } | ||
type: 'polygon', | ||
coordinates: this.transformBackward(geometry, transformOptions)[0] | ||
coordinates: this.transformBackward(geometry, options)[0] | ||
}; | ||
@@ -663,2 +669,47 @@ } | ||
} | ||
/** | ||
* Transforms a GeoJSON FeatureCollection backward to a SVG string | ||
* | ||
* Note: Multi-geometries are not supported | ||
* @param {GeojsonFeatureCollection} geojson - GeoJSON FeatureCollection to transform | ||
* @param {Partial<TransformOptions>} [options] - Transform options | ||
* @returns {string} Backward transform of input, as SVG string | ||
*/ | ||
transformGeojsonFeatureCollectionToSvgString(geojson, options) { | ||
const svgGeometries = []; | ||
for (const geojsonGeometry of featureCollectionToGeometries(geojson)) { | ||
const svgGeometry = this.transformGeojsonToSvg(geojsonGeometry, options); | ||
svgGeometries.push(svgGeometry); | ||
} | ||
return svgGeometriesToSvgString(svgGeometries); | ||
} | ||
assureEqualHandedness(point) { | ||
return this.options?.differentHandedness ? flipY(point) : point; | ||
} | ||
computeTransformation(sourcePoints, destinationPoints) { | ||
if (this.type === 'straight') { | ||
return new Straight(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'helmert') { | ||
return new Helmert(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'polynomial1' || this.type === 'polynomial') { | ||
return new Polynomial(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'polynomial2') { | ||
return new Polynomial(sourcePoints, destinationPoints, 2); | ||
} | ||
else if (this.type === 'polynomial3') { | ||
return new Polynomial(sourcePoints, destinationPoints, 3); | ||
} | ||
else if (this.type === 'projective') { | ||
return new Projective(sourcePoints, destinationPoints); | ||
} | ||
else if (this.type === 'thinPlateSpline') { | ||
return new RBF(sourcePoints, destinationPoints, thinPlateKernel, euclideanNorm); | ||
} | ||
else { | ||
throw new Error(`Unsupported transformation type: ${this.type}`); | ||
} | ||
} | ||
} |
{ | ||
"name": "@allmaps/transform", | ||
"version": "1.0.0-beta.37", | ||
"version": "1.0.0-beta.38", | ||
"contributors": [ | ||
@@ -45,3 +45,3 @@ { | ||
"dependencies": { | ||
"@allmaps/stdlib": "^1.0.0-beta.27", | ||
"@allmaps/stdlib": "^1.0.0-beta.28", | ||
"@turf/distance": "^6.3.0", | ||
@@ -52,14 +52,16 @@ "@turf/midpoint": "^6.3.0", | ||
"devDependencies": { | ||
"@allmaps/types": "^1.0.0-beta.12", | ||
"@allmaps/types": "^1.0.0-beta.13", | ||
"@types/eslint": "^8.56.0", | ||
"@types/geojson": "^7946.0.10", | ||
"@typescript-eslint/eslint-plugin": "^5.45.0", | ||
"@typescript-eslint/parser": "^5.45.0", | ||
"@typescript-eslint/eslint-plugin": "^7.0.0", | ||
"@typescript-eslint/parser": "^7.0.0", | ||
"chai": "^4.3.6", | ||
"chai-shallow-deep-equal": "^1.4.6", | ||
"documentation": "^14.0.0", | ||
"eslint": "^8.35.0", | ||
"eslint": "^8.56.0", | ||
"eslint-config-prettier": "^9.1.0", | ||
"mocha": "^8.4.0", | ||
"prettier": "^2.8.0", | ||
"typescript": "^5.0.0", | ||
"vite": "^4.3.0" | ||
"vite": "^5.0.3" | ||
}, | ||
@@ -82,3 +84,3 @@ "scripts": { | ||
"license": "GPL-3.0-or-later", | ||
"gitHead": "c3a51a2359687465691b99b8c21fb9c20289064c" | ||
"gitHead": "2cd1595a894c77557a492e61371508d615d9f6b8" | ||
} |
297
README.md
@@ -50,6 +50,6 @@ # @allmaps/transform | ||
const transformedPoint = transformer.transformBackward([ | ||
const roundtripTransformedPoint = transformer.transformBackward([ | ||
4.9385700843392435, 52.46580484503631 | ||
]) | ||
// transformedPoint = [100, 100] | ||
// roundtripTransformedPoint = [100, 100] | ||
``` | ||
@@ -62,3 +62,3 @@ | ||
```js | ||
export const transformGcps7 = [ | ||
const transformGcps7 = [ | ||
{ | ||
@@ -94,3 +94,3 @@ source: [0, 0], | ||
const transformOptions = { | ||
const options = { | ||
maxOffsetRatio: 0.001, | ||
@@ -114,3 +114,3 @@ maxDepth: 2 | ||
lineStringGeoJSON, | ||
transformOptions | ||
options | ||
) | ||
@@ -135,3 +135,3 @@ // transformedLineString = [ | ||
```js | ||
export const transformGcps6 = [ | ||
const transformGcps6 = [ | ||
{ | ||
@@ -163,3 +163,3 @@ source: [1344, 4098], | ||
const transformOptions = { | ||
const options = { | ||
maxOffsetRatio: 0.00001, | ||
@@ -182,3 +182,3 @@ maxDepth: 1 | ||
polygon, | ||
transformOptions | ||
options | ||
) | ||
@@ -208,3 +208,3 @@ // const transformedPolygonGeoJSON = { | ||
```js | ||
export const transformGcps7 = [ | ||
const transformGcps7 = [ | ||
{ | ||
@@ -240,3 +240,3 @@ source: [0, 0], | ||
const transformOptions = { | ||
const options = { | ||
inputIsMultiGeometry: true // this assures the transform method recognises the input as a multiPoint, not a LineString | ||
@@ -252,6 +252,3 @@ } | ||
const transformedMultiPoint = transformer.transformForward( | ||
multiPoint, | ||
transformOptions | ||
) | ||
const transformedMultiPoint = transformer.transformForward(multiPoint, options) | ||
// const transformedMultiPoint = [ | ||
@@ -281,3 +278,3 @@ // [31.06060606060611, 155.30303030303048], | ||
All transformer methods accepts Points, LineStrings as well as Polygons (and MultiPoints, MultiLineStrings and MultiPolygons), both as simple geometries or GeoJSON geometries. There are, however, separate methods for transforming to simple geometries or to GeoJSON geometries. There are also separate methods for transforming forward or backward. | ||
All transformer methods accepts Points, LineStrings as well as Polygons (and MultiPoints, MultiLineStrings and MultiPolygons), both as standard geometries or GeoJSON geometries. There are, however, separate methods for transforming to standard geometries or to GeoJSON geometries. There are also separate methods for transforming forward or backward. | ||
@@ -298,10 +295,11 @@ Hence, the main methods are: `transformForward()`, `transformForwardAsGeojson()`, `transformBackward()` and `transformBackwardAsGeojson()` | ||
| Option | Description | Default | | ||
| :------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------- | | ||
| `maxOffsetRatio` | Maximum offset ratio when recursively adding midpoints (smaller means more midpoints) | `0` | | ||
| `maxDepth` | Maximum recursion depth when recursively adding midpoints (higher means more midpoints) | `0` (i.e. no midpoints by default!) | | ||
| `sourceIsGeographic` | Use geographic distances and midpoints for lon-lat source points | `false` (`true` when source is GeoJSON) | | ||
| `destinationIsGeographic` | Use geographic distances and midpoints for lon-lat destination points | `false` (`true` when destination is GeoJSON) | | ||
| `inputIsMultiGeometry` | Whether the input should be considered as a MultiPoint, MultiLineString or MultiPolygon. This is necessary since the simple geometry (as opposed to GeoJSON geometries) types are not deterministic: the types of LineString and MultiPoint are identical. | `false` | | ||
| `differentHandedness` | Whether one of the axes should be flipped while computing the transformation parameters. Should be true if the handedness differs between the source and destination. | `false` | | ||
| Option | Description | Type | Default | | ||
| :------------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- | :------------------------------------------- | | ||
| `maxOffsetRatio` | Maximum offset ratio when recursively adding midpoints (smaller means more midpoints) | `number` | `0` | | ||
| `maxDepth` | Maximum recursion depth when recursively adding midpoints (higher means more midpoints) | `number` | `0` (i.e. no midpoints by default!) | | ||
| `sourceIsGeographic` | Use geographic distances and midpoints for lon-lat source points | `boolean` | `false` (`true` when source is GeoJSON) | | ||
| `destinationIsGeographic` | Use geographic distances and midpoints for lon-lat destination points | `boolean` | `false` (`true` when destination is GeoJSON) | | ||
| `inputIsMultiGeometry` | Whether the input should be considered as a MultiPoint, MultiLineString or MultiPolygon. This is necessary since the standard geometry (as opposed to GeoJSON geometries) types are not deterministic: the types of LineString and MultiPoint are identical. | `boolean` | `false` | | ||
| `differentHandedness` | Whether one of the axes should be flipped while computing the transformation parameters. Should be true if the handedness differs between the source and destination. | `boolean` | `false` | | ||
| `evaluationType` | Whether to evaluate the transformation function or one of it's derivatives. | `'function' \| 'partialDerivativeX' \| 'partialDerivativeY'` | `'function'` | | ||
@@ -328,2 +326,49 @@ #### Recursively adding midpoints | ||
#### Distortions | ||
Some transformations may induce distortions. Let's consider transforming an image to make this more visual. It we take a Helmert transformation of an image, we will see that it doesn't distort the image much: it will scale, rotate and translate the image, but not shear it (angles are preserved) - the only distortion applied is the scaling, and that scaling is the same everywhere across the image. If, on the other hand, we take a Thin Plate Spline transformation (with many GCPs) of that same image, we will see that the image will be distorted much, and will look like a rubber sheet which has been pulled and deformed in many different locations. Every pixel will be distorted in a unique way, such that both the areas and angles of the original image are not preserved. | ||
We can compute these distortions locally, at every point. The approach implemented here is based on the theory of **'Differential Distortion Analysis'**: by evaluating the partial derivatives of the transformation function at every point we can compute local distortion measures from these derivatives, such as the **area distortion** `log2sigma` and **angular distortion** `twoOmega`. These will tell us how much the area and angles are distortion at every point. Thereafter averaging over all points can give un an indication of the overall distortion. | ||
'Differential Distortion Analysis' was earlier implemented in [this](https://github.com/mclaeysb/distortionAnalysis) Matlab/Octave package following peer reviewed publications of both the theoretical approach an an application to a historical map. | ||
This packages supports the evaluation of the partial derivatives in the `transformForward()` and `transformBackward()` functions via their transform options, and exports a function `computeDistortionFromPartialDerivatives()` to compute the distortion measures from these partial derivatives. The supported distortion measures are available via the exported `supportedDistortionMeasures` constant. These include: | ||
| Key | Type | Description | Example | | ||
| ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | ||
| `log2sigma` | Area distortion measure | The base-2 logarithm of the area scale factor σ, which indicates how much a local infinitesimal surface element is enlarged on the map (relative to the map’s scale). | `0` for no area distortion, `1` if the area is twice as big, `-1` if the are is twice as small after transformation. | | ||
| `twoOmega` | Angular distortion measure | The maximum angular distortion 2Ω, which indicated the maximal (taken over all possible angles between two direction from that point) difference between an angle before and after the transformation, making it a measure for shearing. | `0` for no angular distortion, `>0` for angular distortion. | | ||
| `airyKavr` | Airy-Kavrayskiy distortion measure | A measure combining the effects of areal and angular distortion. | `0` for no distortion, `>0` for distortion. | | ||
| `signDetJ` | Flip measure | The transformation's Jacobian determinant flipping sign, describing 'fold-over' of the transformation. | `1` for no flip, `-1` for flip. | | ||
| `thetaa` | Tissot indicatrix axis | The angle between the major axis of the Tissot indicatrix and the cartesian x-axis. | `0` for no rotation, `>0` for rotation. | | ||
Here's an example on how to compute local distortion. | ||
```js | ||
import { GcpTransformer, computeDistortionFromPartialDerivatives } from '@allmaps/transform' | ||
const transformGcps6 = ... // See above | ||
const helmertTransformer = new GcpTransformer(transformGcps6, 'helmert') | ||
helmertTransformer.createForwardTransformation() | ||
const referenceScale = helmertTransformer.forwardTransformation.scale | ||
const transformer = new GcpTransformer(transformGcps6, 'thinPlateSpline') | ||
const input = [1000, 1000] | ||
const partialDerivativeX = transformer.transformForward(input, { | ||
evaluationType: 'partialDerivativeX' | ||
}) | ||
const partialDerivativeY = transformer.transformForward(input, { | ||
evaluationType: 'partialDerivativeY' | ||
}) | ||
const distortion = computeDistortionFromPartialDerivatives( | ||
partialDerivativeX, | ||
partialDerivativeY, | ||
'log2sigma', | ||
referenceScale | ||
) | ||
// distortion = 1.7800137112938559 | ||
// => At this input location the area has significantly expanded after the transformation | ||
``` | ||
## Notes | ||
@@ -353,5 +398,5 @@ | ||
#### Geometries | ||
#### Geometry types | ||
This uses the same geometry types as used in other packages. The simple geometries are: | ||
**Standard geometries**: the following geometry types are used by default in this and other packages. | ||
@@ -368,10 +413,11 @@ ```ts | ||
export type MultiPoint = Point[] | ||
type MultiPoint = Point[] | ||
// Notice that this is equivalent to the LineString type, hence the `inputIsMultiGeometry` option | ||
export type MultiLineString = Point[][] | ||
type MultiLineString = Point[][] | ||
// Notice that this is equivalent to the Polygon type, hence the `inputIsMultiGeometry` option | ||
export type MultiPolygon = Point[][][] | ||
type MultiPolygon = Point[][][] | ||
export type Geometry = | ||
type Geometry = | ||
| Point | ||
@@ -385,4 +431,45 @@ | LineString | ||
GeoJSON geometries follow the [GeoJSON specification](https://geojson.org/). | ||
**GeoJSON geometries** follow the [GeoJSON specification](https://geojson.org/). | ||
**SVG geometries** are expressed using the following types (but note that some functions allow svg's to be passed as a string): | ||
```js | ||
export type SvgCircle = { | ||
type: 'circle' | ||
attributes?: SvgAttributes | ||
coordinates: Point | ||
} | ||
export type SvgLine = { | ||
type: 'line' | ||
attributes?: SvgAttributes | ||
coordinates: [Point, Point] | ||
} | ||
export type SvgPolyLine = { | ||
type: 'polyline' | ||
attributes?: SvgAttributes | ||
coordinates: Point[] | ||
} | ||
export type SvgPolygon = { | ||
type: 'polygon' | ||
attributes?: SvgAttributes | ||
coordinates: Point[] | ||
} | ||
export type SvgRect = { | ||
type: 'rect' | ||
attributes?: SvgAttributes | ||
coordinates: Point[] | ||
} | ||
export type SvgGeometry = | ||
| SvgCircle | ||
| SvgLine | ||
| SvgPolyLine | ||
| SvgPolygon | ||
| SvgRect | ||
``` | ||
### Transform vs. GDAL | ||
@@ -394,2 +481,47 @@ | ||
## Notes | ||
* Only **linearly independent control points** should be considered when checking if the criterion for the minimum number of control points is met. For example, three control points that are collinear (one the same line) only count as two linearly independent points. The current implementation doesn't check such linear (in)dependance, but building a transformer with insufficient linearly independent control points will result in a badly conditioned matrix (no error but diverging results) or non-invertible matrix (**error when inverting matrix**). | ||
* The transform functions are map-projection agnostic: they describe a transformation for one cartesian `(x, y)` plane to another. Using control points with `(longitude, latitude)` coordinates will produce a transformation from or to the cartesian plane of an equirectangular projection. (The only semi-exception to this is when using the `destinationIsGeographic` and `sourceIsGeographic` parameters - although these consider coordinates as lying on a sphere more than as projection coordinates.) | ||
## CLI | ||
The [@allmaps/cli](../../apps/cli/) package creates and interface for four specific use cases: | ||
* Transforming points to points. | ||
* Transforming **SVG** geometries from the resource coordinates space of a IIIF resource to **GeoJSON** objects in the geo coordinate space of an interactive map. | ||
* Transforming **GeoJSON** objects from the geo coordinate space of an interactive map to **SVG** objects in the resource coordinates space of a IIIF resource, **given (the GCPs and transformation type from) a Georeference Annotation** | ||
* Vice versa: transforming **SVG** objects from the resource coordinates to **GeoJSON** objects in the geo coordinate space. | ||
* Transforming the **SVG resource mask** included in a Georeference Annotation to a GeoJSON Polygon. | ||
## Benchmark | ||
Here are some benchmarks on building and using a transformer, as computed on a 2023 MacBook Air M2. | ||
Creating a transformer (with 10 points) (and transform 1 point) | ||
| Type | Options | Ops/s | | ||
| ----------------- | ---------- | ------ | | ||
| `helmert` | | 64004 | | ||
| `polynomial` | `order: 1` | 134372 | | ||
| `polynomial` | `order: 2` | 66312 | | ||
| `polynomial` | `order: 3` | 26601 | | ||
| `thinPlateSpline` | | 20422 | | ||
| `projective` | | 27430 | | ||
Using a transformer (with 10 points) to transform 1 point | ||
| Type | Options | Ops/s | | ||
| ----------------- | ---------- | -------- | | ||
| `helmert` | | 13062520 | | ||
| `polynomial` | `order: 1` | 12613152 | | ||
| `polynomial` | `order: 2` | 12052005 | | ||
| `polynomial` | `order: 3` | 3440201 | | ||
| `thinPlateSpline` | | 3037781 | | ||
| `projective` | | 11950188 | | ||
See [`./bench/index.js`](`./bench/index.js`). | ||
The benchmark can be run with `pnpm run bench`. | ||
## API | ||
@@ -404,2 +536,4 @@ | ||
* [Parameters](#parameters) | ||
* [createForwardTransformation](#createforwardtransformation) | ||
* [createBackwardTransformation](#createbackwardtransformation) | ||
* [transformForward](#transformforward) | ||
@@ -414,3 +548,9 @@ * [transformForwardAsGeojson](#transformforwardasgeojson) | ||
* [transformSvgToGeojson](#transformsvgtogeojson) | ||
* [transformSvgStringToGeojsonFeatureCollection](#transformsvgstringtogeojsonfeaturecollection) | ||
* [transformGeojsonToSvg](#transformgeojsontosvg) | ||
* [transformGeojsonFeatureCollectionToSvgString](#transformgeojsonfeaturecollectiontosvgstring) | ||
* [Transformation](#transformation) | ||
* [Parameters](#parameters-13) | ||
* [computeDistortionFromPartialDerivatives](#computedistortionfrompartialderivatives) | ||
* [Parameters](#parameters-14) | ||
@@ -430,2 +570,10 @@ ### allmaps/transform | ||
#### createForwardTransformation | ||
Create forward transformation | ||
#### createBackwardTransformation | ||
Create backward transformation | ||
#### transformForward | ||
@@ -438,3 +586,3 @@ | ||
* `input` **(Geometry | GeojsonGeometry)** Geometry or GeoJSON geometry to transform | ||
* `options` **PartialTransformOptions?** Transform options | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -450,3 +598,3 @@ Returns **Geometry** Forward transform of input as Geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Geometry or GeoJSON geometry to transform | ||
* `options` **PartialTransformOptions?** Transform options | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -462,3 +610,3 @@ Returns **GeojsonGeometry** Forward transform of input, as GeoJSON geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Geometry or GeoJSON geometry to transform | ||
* `options` **PartialTransformOptions?** Transform options | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -474,3 +622,3 @@ Returns **Geometry** backward transform of input, as geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Geometry or GeoJSON geometry to transform | ||
* `options` **PartialTransformOptions?** Transform options | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -486,3 +634,3 @@ Returns **GeojsonGeometry** backward transform of input, as GeoJSON geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Input to transform | ||
* `options`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -498,3 +646,3 @@ Returns **Geometry** Forward transform of input, as Geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Input to transform | ||
* `options`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -510,3 +658,3 @@ Returns **Geometry** Forward transform of input, as GeoJSON geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Input to transform | ||
* `options`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -522,3 +670,3 @@ Returns **Geometry** Backward transform of input, as a Geometry | ||
* `input` **(Geometry | GeojsonGeometry)** Input to transform | ||
* `options`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
@@ -536,6 +684,19 @@ Returns **GeojsonGeometry** Backward transform of input, as a GeoJSON geometry | ||
* `geometry` **SvgGeometry** SVG geometry to transform | ||
* `transformOptions`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
Returns **GeojsonGeometry** Forward transform of input, as a GeoJSON geometry | ||
#### transformSvgStringToGeojsonFeatureCollection | ||
Transforms a SVG string forward to a GeoJSON FeatureCollection | ||
Note: Multi-geometries are not supported | ||
##### Parameters | ||
* `svg` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** SVG string to transform | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
Returns **GeojsonFeatureCollection** Forward transform of input, as a GeoJSON FeatureCollection | ||
#### transformGeojsonToSvg | ||
@@ -550,49 +711,41 @@ | ||
* `geometry` **GeojsonGeometry** GeoJSON geometry to transform | ||
* `transformOptions`   | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
Returns **SvgGeometry** Backward transform of input, as SVG geometry | ||
## Notes | ||
#### transformGeojsonFeatureCollectionToSvgString | ||
* Only **linearly independent control points** should be considered when checking if the criterion for the minimum number of control points is met. For example, three control points that are collinear (one the same line) only count as two linearly independent points. The current implementation doesn't check such linear (in)dependance, but building a transformer with insufficient linearly independent control points will result in a badly conditioned matrix (no error but diverging results) or non-invertible matrix (**error when inverting matrix**). | ||
* The transform functions are map-projection agnostic: they describe a transformation for one cartesian `(x, y)` plane to another. Using control points with `(longitude, latitude)` coordinates will produce a transformation from or to the cartesian plane of an equirectangular projection. (The only semi-exception to this is when using the `destinationIsGeographic` and `sourceIsGeographic` parameters - although these consider coordinates as lying on a sphere more than as projection coordinates.) | ||
Transforms a GeoJSON FeatureCollection backward to a SVG string | ||
## CLI | ||
Note: Multi-geometries are not supported | ||
The [@allmaps/cli](../../apps/cli/) package creates and interface for four specific use cases: | ||
##### Parameters | ||
* Transforming points to points. | ||
* Transforming **SVG** geometries from the resource coordinates space of a IIIF resource to **GeoJSON** objects in the geo coordinate space of an interactive map. | ||
* Transforming **GeoJSON** objects from the geo coordinate space of an interactive map to **SVG** objects in the resource coordinates space of a IIIF resource, **given (the GCPs and transformation type from) a Georeference Annotation** | ||
* Vice versa: transforming **SVG** objects from the resource coordinates to **GeoJSON** objects in the geo coordinate space. | ||
* Transforming the **SVG resource mask** included in a Georeference Annotation to a GeoJSON Polygon. | ||
* `geojson` **GeojsonFeatureCollection** GeoJSON FeatureCollection to transform | ||
* `options` **Partial\<TransformOptions>?** Transform options | ||
## Benchmark | ||
Returns **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** Backward transform of input, as SVG string | ||
Here are some benchmarks on building and using a transformer, as computed on a 2023 MacBook Air M2. | ||
### Transformation | ||
Creating a transformer (with 10 points) (and transform 1 point) | ||
Transformation class. Abstract class, extended by the various transformations. | ||
| Type | Options | Ops/s | | ||
| ----------------- | ---------- | ------ | | ||
| `helmert` | | 71338 | | ||
| `polynomial` | `order: 1` | 163419 | | ||
| `polynomial` | `order: 2` | 86815 | | ||
| `polynomial` | `order: 3` | 33662 | | ||
| `thinPlateSpline` | | 27905 | | ||
| `projective` | | 36202 | | ||
#### Parameters | ||
Using a transformer (with 10 points) to transform 1 point | ||
* `sourcePoints` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)\<Point>** The source points | ||
* `destinationPoints` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)\<Point>** The destination points | ||
* `type` **TransformationType** The transformation type | ||
* `pointCountMinimum` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** The minimum number of points for the transformation type | ||
| Type | Options | Ops/s | | ||
| ----------------- | ---------- | -------- | | ||
| `helmert` | | 27398212 | | ||
| `polynomial` | `order: 1` | 22364872 | | ||
| `polynomial` | `order: 2` | 19126410 | | ||
| `polynomial` | `order: 3` | 3925102 | | ||
| `thinPlateSpline` | | 484141 | | ||
| `projective` | | 22657850 | | ||
### computeDistortionFromPartialDerivatives | ||
See [`./bench/index.js`](`./bench/index.js`). | ||
Compute distortion from partial derivatives | ||
The benchmark can be run with `pnpm run bench`. | ||
#### Parameters | ||
* `partialDerivativeX` **Point** the partial derivative to 'x' of the transformation, evaluated at a set point | ||
* `partialDerivativeY` **Point** the partial derivative to 'x' of the transformation, evaluated at a set point | ||
* `distortionMeasure` **DistortionMeasure?** the requested distortion measure, or undefined to return 0 | ||
* `referenceScale` **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** the reference area scaling (sigma) to take into account, e.g. computed via a helmert transform (optional, default `1`) | ||
Returns **[number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** the distortion measure at the set point |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
1203077
34
6775
722
14