Прежде всего, поскольку выходные координаты 2D, я собираюсь предположить, что мы можем избавиться от информации о высоте из входных координат. Таким образом, входные данные состоят из четырех точек, определяющих входной прямоугольник:
P1(lat, lon)
, P2(lat, lon)
, P3(lat, lon)
, P4(lat, lon)
и размеры выходного прямоугольника: w
,h
.
Я также собираюсь игнорировать кривизну Земли (футбольное поле достаточно маленькое). С этими допущениями мы можем реализовать функцию преобразования, выполнив аффинное преобразование. Было бы расточительно создавать матрицу преобразования каждый раз, когда мы хотим выполнить преобразование. По этой причине нам нужны две функции: первая для создания матрицы преобразования (вызывается только один раз) и вторая, которая будет использовать эту матрицу для выполнения самого преобразования (возможно, вызывается много раз, по одному разу для каждой точки, которую мы хотим преобразовать), что-то вроде:
tm = createTransformationMatrix(P1, P2, P4, w, h)
inPoint = (200, 50)
outPoint = transform(inPoint, tm)
Обратите внимание, что нам нужны только три из четырех входных точек, чтобы однозначно определить повернутый прямоугольник в двумерном евклидовом пространстве.
Вот реализация createTransformationMatrix
и transform
функций:
const run = function() {
// Creates transformation matrix to transform
// from rectangle somewhere in 2D space with coordinates p0, px, pi, py
// to rectangle with coordinates (x=0, y=0), (x=w, y=0), (x=w, y=h), (x=0, y=h).
// Note that: p0 is mapped to (x=0, y=0)
// px is mapped to (x=w, y=0)
// py is mapped to (x=0, y=h)
const createTransformationMatrix = function(p0, px, py, w, h) {
// Translate px and py by p0 - pxt and pyt are px and py vectors in coordinate system in which p0 is at the origin
const pxt = {
x: px.x - p0.x,
y: px.y - p0.y,
};
const pyt = {
x: py.x - p0.x,
y: py.y - p0.y,
};
// Create transformation matrix, which is inverse of transformation matrix that:
// 1. Transforms (x=0, y=0) to (x=p0.x, y=p0.y)
// 2. Transforms (x=1, y=0) to (x=p0.x + pxt.x / w, y=p0.y + pxt.y / w)
// 3. Transforms (x=0, y=1) to (x=p0.x + pyt.x / h, y=p0.y + pyt.y / h)
return Matrix.invert3([
[pxt.x / w, pyt.x / h, p0.x],
[pxt.y / w, pyt.y / h, p0.y],
[0 , 0 , 1 ],
]);
};
const transform = function(point, transformationMatrix) {
// Convert point to homogeneous coordinates
const inputVector = [
[point.x],
[point.y],
[1],
];
// Transform inputVector
const outputVector = Matrix.multiply(transformationMatrix, inputVector);
// Convert outputVector back to cartesian coordinates and return
return {
x: outputVector[0][0] / outputVector[2][0],
y: outputVector[1][0] / outputVector[2][0],
};
};
const w = 220;
const h = 115;
const p1 = {x:-79, y:80 };
const p2 = {x:9, y:-96};
const p3 = {x:55, y:-72};
const p4 = {x:-34, y:105};
const tm = createTransformationMatrix(p1, p2, p4, w, h);
const inPoint = {x: 200, y: 50};
const outPoint = transform(inPoint, tm);
console.log(`(${inPoint.x}, ${inPoint.y}) --[transform]--> (${outPoint.x}, ${outPoint.y})`);
}
//// Matrix ////
const Matrix = {};
Matrix.scale = (s, m) => m.map(x => Array.isArray(x) ? Matrix.scale(s, x) : s * x);
Matrix.multiply = function(a, b) {
const aNumRows = a.length, aNumCols = a[0].length;
const bNumRows = b.length, bNumCols = b[0].length;
const m = new Array(aNumRows);
for (let r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols);
for (let c = 0; c < bNumCols; ++c) {
m[r][c] = 0;
for (let i = 0; i < aNumCols; ++i)
m[r][c] += a[r][i] * b[i][c];
}
}
return m;
};
Matrix.invert3 = function(m) {
const [[a, b, c],
[d, e, f],
[g, h, i]] = m;
const det = a*(e*i - f*h) - b*(d*i - f*g) + c*(d*h - e*g);
return Matrix.scale(1/det, [
[e*i - f*h, c*h - b*i, b*f - c*e],
[f*g - d*i, a*i - c*g, c*d - a*f],
[d*h - e*g, b*g - a*h, a*e - b*d],
]);
};
//////////////
run();
Я включил всю логику обработки матрицы, так что этот фрагмент кода является самодостаточным, но я бы предложил вместо этого использовать некоторую библиотеку линейной алгебры для обработки матрицы.
Я также сделал более наглядной демонстрацией .