Search code examples
coordinatesgeo

How to convert a location on football pitch to coordinates on rectangle?


I have 4 points of football pitch (corner points):

P1(lat, lon, alt), P2(lat, lon, alt), P3(lat, lon, alt), P4(lat, lon, alt).

and a location on the pitch:

L(lat, lon, alt)

I want to convert L(lat, lon, alt) to L(x, y) on a rectangle with size of (W, H).

How to implement this conversion function? (I preferred C# language but implementation language is not important)

The following image describes my problem (I don't know how to implement the Function box):

enter image description here


Solution

  • First off, because output coordinates are 2D, I'm going to assume that we can get rid of altitude information from input coordinates. So input data consist of four points defining the input rectangle:

    P1(lat, lon), P2(lat, lon), P3(lat, lon), P4(lat, lon)

    and dimensions of the output rectangle: w, h.

    I'm also going to ignore the curvature of the Earth (football pitch is small enough). With those assumptions we can implement the conversion function, by performing affine transformation. It would be wasteful to create transformation matrix each time we want to perform a transformation. For that reason we need two functions: first one to create the transformation matrix (called only once), and the second one that will use that matrix to perform transformation itself (called possibly many times, one time for each point we want to transform), something like:

    tm = createTransformationMatrix(P1, P2, P4, w, h)
    inPoint = (200, 50)
    outPoint = transform(inPoint, tm)
    

    Note that we only need three of four input points to unambiguously define a rotated rectangle in 2D euclidean space.

    Here is the implementation of createTransformationMatrix and transform functions:

    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();

    I've included all the matrix processing logic, so that this code snippet is self contained, but I would suggest you to instead use some linear algebra library for matrix processing.

    I've also made a more visual demo.