类名 common/util/videoMap/GDALCoordTransform.js
import Log from "../Log"

/**
 * The resulting geotransform is the equivalent to padfGT1 and then padfGT2 being applied to a point.
 * @param {Float64Array} padfGT1 the first geotransform, six values.
 * @param {Float64Array} padfGT2 the second geotransform, six values.
 * @param {Float64Array} padfGTOut the output geotransform, six values, may safely be the same array as padfGT1 or padfGT2.
 */
export function GDALComposeGeoTransforms(padfGT1, padfGT2, padfGTOut) {
  // We need to think of the geotransform in a more normal form to do
  // the matrix multiple:
  //
  //  __                     __
  //  | gt[1]   gt[2]   gt[0] |
  //  | gt[4]   gt[5]   gt[3] |
  //  |  0.0     0.0     1.0  |
  //  --                     --
  //
  // Then we can use normal matrix multiplication to produce the
  // composed transformation.  I don't actually reform the matrix
  // explicitly which is why the following may seem kind of spagettish.

  padfGTOut[1] = padfGT2[1] * padfGT1[1] + padfGT2[2] * padfGT1[4]
  padfGTOut[2] = padfGT2[1] * padfGT1[2] + padfGT2[2] * padfGT1[5]
  padfGTOut[0] =
    padfGT2[1] * padfGT1[0] + padfGT2[2] * padfGT1[3] + padfGT2[0] * 1.0

  padfGTOut[4] = padfGT2[4] * padfGT1[1] + padfGT2[5] * padfGT1[4]
  padfGTOut[5] = padfGT2[4] * padfGT1[2] + padfGT2[5] * padfGT1[5]
  padfGTOut[3] =
    padfGT2[4] * padfGT1[0] + padfGT2[5] * padfGT1[3] + padfGT2[3] * 1.0
}
/**
 * Invert Geotransform.
 * @param {Float64Array} gt_in Input geotransform (six doubles - unaltered).
 * @param {Float64Array} gt_out Output geotransform (six doubles - updated).
 * @returns {Number} TRUE on success or FALSE if the equation is uninvertable.
 */
export function GDALInvGeoTransform(gt_in, gt_out) {
  // Special case - no rotation - to avoid computing determinate
  // and potential precision issues.
  if (
    gt_in[2] === 0.0 &&
    gt_in[4] === 0.0 &&
    gt_in[1] !== 0.0 &&
    gt_in[5] !== 0.0
  ) {
    /*X = gt_in[0] + x * gt_in[1]
      Y = gt_in[3] + y * gt_in[5]
      -->
      x = -gt_in[0] / gt_in[1] + (1 / gt_in[1]) * X
      y = -gt_in[3] / gt_in[5] + (1 / gt_in[5]) * Y
    */
    gt_out[0] = -gt_in[0] / gt_in[1]
    gt_out[1] = 1.0 / gt_in[1]
    gt_out[2] = 0.0
    gt_out[3] = -gt_in[3] / gt_in[5]
    gt_out[4] = 0.0
    gt_out[5] = 1.0 / gt_in[5]
    return 1
  }
  // Assume a 3rd row that is [1 0 0].

  // Compute determinate.
  const det = gt_in[1] * gt_in[5] - gt_in[2] * gt_in[4]
  const magnitude = Math.max(
    Math.max(Math.abs(gt_in[1]), Math.abs(gt_in[2])),
    Math.max(Math.abs(gt_in[4]), Math.abs(gt_in[5]))
  )

  if (Math.abs(det) <= 1e-10 * magnitude * magnitude) return 0

  const inv_det = 1.0 / det

  // Compute adjoint, and divide by determinate.

  gt_out[1] = gt_in[5] * inv_det
  gt_out[4] = -gt_in[4] * inv_det

  gt_out[2] = -gt_in[2] * inv_det
  gt_out[5] = gt_in[1] * inv_det

  gt_out[0] = (gt_in[2] * gt_in[3] - gt_in[0] * gt_in[5]) * inv_det
  gt_out[3] = (-gt_in[1] * gt_in[3] + gt_in[0] * gt_in[4]) * inv_det

  return 1
}
/**
 * Applies the following computation, converting a (pixel, line) coordinate into a georeferenced (geo_x, geo_y) location.
 * @param {Float64Array} padfGeoTransform Six coefficient GeoTransform to apply.
 * @param {Number} dfPixel Input pixel position.
 * @param {Number} dfLine Input line position.
 * @returns {Object} output location
 */
export function GDALApplyGeoTransform(padfGeoTransform, dfPixel, dfLine) {
  const pdfGeoX =
    padfGeoTransform[0] +
    dfPixel * padfGeoTransform[1] +
    dfLine * padfGeoTransform[2]

  const pdfGeoY =
    padfGeoTransform[3] +
    dfPixel * padfGeoTransform[4] +
    dfLine * padfGeoTransform[5]

  return { pdfGeoX, pdfGeoY }
}

/**
 * Ground Control Point
 * @typedef GCP
 * @type {Object}
 * @property {String} pszId Unique identifier, often numeric
 * @property {String} pszInfo Informational message or ""
 * @property {Number} dfGCPPixel Pixel (x) location of GCP on raster
 * @property {Number} dfGCPLine Line (y) location of GCP on raster
 * @property {Number} dfGCPX X position of GCP in georeferenced space
 * @property {Number} dfGCPY Y position of GCP in georeferenced space
 * @property {Number} dfGCPZ Elevation of GCP, or zero if not known
 */

/**
 * Given a set of GCPs perform first order fit as a geotransform.
 * @param {GCP[]} pasGCPs the list of GCP structures.
 * @param {Float64Array} padfGeoTransform the six double array in which the affine geotransformation will be returned.
 * @param {Boolean} bApproxOK If FALSE the function will fail if the geotransform is not essentially an exact fit (within 0.25 pixel) for all GCPs.
 * @return {Boolean} TRUE on success or FALSE if there aren't enough points to prepare a geotransform, the pointers are ill-determined or if bApproxOK is FALSE and the fit is poor.
 */
export function GDALGCPsToGeoTransform(pasGCPs, padfGeoTransform, bApproxOK) {
  let dfPixelThreshold = 0.25
  if (!bApproxOK) {
    // 这里是在读取配置信息 为方便前端实现逻辑默认开启
    bApproxOK = true
    dfPixelThreshold = 0.25
    // bApproxOK = CPLTestBool(
    //     CPLGetConfigOption("GDAL_GCPS_TO_GEOTRANSFORM_APPROX_OK", "NO"));
    // if (!bApproxOK)
    // {
    //     // coverity[tainted_data]
    //     dfPixelThreshold = CPLAtof(CPLGetConfigOption(
    //         "GDAL_GCPS_TO_GEOTRANSFORM_APPROX_THRESHOLD", "0.25"));
    // }
  }
  const nGCPCount = pasGCPs.length
  if (nGCPCount < 2) {
    return false
  }

  /* -------------------------------------------------------------------- */
  /*      Recognise a few special cases.                                  */
  /* -------------------------------------------------------------------- */
  if (nGCPCount === 2) {
    if (
      pasGCPs[1].dfGCPPixel == pasGCPs[0].dfGCPPixel ||
      pasGCPs[1].dfGCPLine == pasGCPs[0].dfGCPLine
    )
      return false

    padfGeoTransform[1] =
      (pasGCPs[1].dfGCPX - pasGCPs[0].dfGCPX) /
      (pasGCPs[1].dfGCPPixel - pasGCPs[0].dfGCPPixel)
    padfGeoTransform[2] = 0.0

    padfGeoTransform[4] = 0.0
    padfGeoTransform[5] =
      (pasGCPs[1].dfGCPY - pasGCPs[0].dfGCPY) /
      (pasGCPs[1].dfGCPLine - pasGCPs[0].dfGCPLine)

    padfGeoTransform[0] =
      pasGCPs[0].dfGCPX -
      pasGCPs[0].dfGCPPixel * padfGeoTransform[1] -
      pasGCPs[0].dfGCPLine * padfGeoTransform[2]

    padfGeoTransform[3] =
      pasGCPs[0].dfGCPY -
      pasGCPs[0].dfGCPPixel * padfGeoTransform[4] -
      pasGCPs[0].dfGCPLine * padfGeoTransform[5]

    return true
  }

  /* -------------------------------------------------------------------- */
  /*      Special case of 4 corner coordinates of a non-rotated           */
  /*      image.  The points must be in TL-TR-BR-BL order for now.        */
  /*      This case helps avoid some imprecision in the general           */
  /*      calculations.                                                   */
  /* -------------------------------------------------------------------- */
  if (
    nGCPCount == 4 &&
    pasGCPs[0].dfGCPLine == pasGCPs[1].dfGCPLine &&
    pasGCPs[2].dfGCPLine == pasGCPs[3].dfGCPLine &&
    pasGCPs[0].dfGCPPixel == pasGCPs[3].dfGCPPixel &&
    pasGCPs[1].dfGCPPixel == pasGCPs[2].dfGCPPixel &&
    pasGCPs[0].dfGCPLine != pasGCPs[2].dfGCPLine &&
    pasGCPs[0].dfGCPPixel != pasGCPs[1].dfGCPPixel &&
    pasGCPs[0].dfGCPY == pasGCPs[1].dfGCPY &&
    pasGCPs[2].dfGCPY == pasGCPs[3].dfGCPY &&
    pasGCPs[0].dfGCPX == pasGCPs[3].dfGCPX &&
    pasGCPs[1].dfGCPX == pasGCPs[2].dfGCPX &&
    pasGCPs[0].dfGCPY != pasGCPs[2].dfGCPY &&
    pasGCPs[0].dfGCPX != pasGCPs[1].dfGCPX
  ) {
    padfGeoTransform[1] =
      (pasGCPs[1].dfGCPX - pasGCPs[0].dfGCPX) /
      (pasGCPs[1].dfGCPPixel - pasGCPs[0].dfGCPPixel)
    padfGeoTransform[2] = 0.0
    padfGeoTransform[4] = 0.0
    padfGeoTransform[5] =
      (pasGCPs[2].dfGCPY - pasGCPs[1].dfGCPY) /
      (pasGCPs[2].dfGCPLine - pasGCPs[1].dfGCPLine)

    padfGeoTransform[0] =
      pasGCPs[0].dfGCPX - pasGCPs[0].dfGCPPixel * padfGeoTransform[1]
    padfGeoTransform[3] =
      pasGCPs[0].dfGCPY - pasGCPs[0].dfGCPLine * padfGeoTransform[5]
    return true
  }

  /* -------------------------------------------------------------------- */
  /*      Compute source and destination ranges so we can normalize       */
  /*      the values to make the least squares computation more stable.   */
  /* -------------------------------------------------------------------- */
  let min_pixel = pasGCPs[0].dfGCPPixel
  let max_pixel = pasGCPs[0].dfGCPPixel
  let min_line = pasGCPs[0].dfGCPLine
  let max_line = pasGCPs[0].dfGCPLine
  let min_geox = pasGCPs[0].dfGCPX
  let max_geox = pasGCPs[0].dfGCPX
  let min_geoy = pasGCPs[0].dfGCPY
  let max_geoy = pasGCPs[0].dfGCPY

  for (let i = 1; i < nGCPCount; ++i) {
    min_pixel = Math.min(min_pixel, pasGCPs[i].dfGCPPixel)
    max_pixel = Math.max(max_pixel, pasGCPs[i].dfGCPPixel)
    min_line = Math.min(min_line, pasGCPs[i].dfGCPLine)
    max_line = Math.max(max_line, pasGCPs[i].dfGCPLine)
    min_geox = Math.min(min_geox, pasGCPs[i].dfGCPX)
    max_geox = Math.max(max_geox, pasGCPs[i].dfGCPX)
    min_geoy = Math.min(min_geoy, pasGCPs[i].dfGCPY)
    max_geoy = Math.max(max_geoy, pasGCPs[i].dfGCPY)
  }

  const EPS = 1.0e-12

  if (
    Math.abs(max_pixel - min_pixel) < EPS ||
    Math.abs(max_line - min_line) < EPS ||
    Math.abs(max_geox - min_geox) < EPS ||
    Math.abs(max_geoy - min_geoy) < EPS
  ) {
    return false // degenerate in at least one dimension.
  }

  const pl_normalize = new Float64Array(6),
    geo_normalize = new Float64Array(6)

  pl_normalize[0] = -min_pixel / (max_pixel - min_pixel)
  pl_normalize[1] = 1.0 / (max_pixel - min_pixel)
  pl_normalize[2] = 0.0
  pl_normalize[3] = -min_line / (max_line - min_line)
  pl_normalize[4] = 0.0
  pl_normalize[5] = 1.0 / (max_line - min_line)

  geo_normalize[0] = -min_geox / (max_geox - min_geox)
  geo_normalize[1] = 1.0 / (max_geox - min_geox)
  geo_normalize[2] = 0.0
  geo_normalize[3] = -min_geoy / (max_geoy - min_geoy)
  geo_normalize[4] = 0.0
  geo_normalize[5] = 1.0 / (max_geoy - min_geoy)
  /* -------------------------------------------------------------------- */
  /* In the general case, do a least squares error approximation by       */
  /* solving the equation Sum[(A - B*x + C*y - Lon)^2] = minimum          */
  /* -------------------------------------------------------------------- */

  let sum_x = 0.0
  let sum_y = 0.0
  let sum_xy = 0.0
  let sum_xx = 0.0
  let sum_yy = 0.0
  let sum_Lon = 0.0
  let sum_Lonx = 0.0
  let sum_Lony = 0.0
  let sum_Lat = 0.0
  let sum_Latx = 0.0
  let sum_Laty = 0.0

  for (let i = 0; i < nGCPCount; ++i) {
    const { pdfGeoX: pixel, pdfGeoY: line } = GDALApplyGeoTransform(
      pl_normalize,
      pasGCPs[i].dfGCPPixel,
      pasGCPs[i].dfGCPLine
    )
    const { pdfGeoX: geox, pdfGeoY: geoy } = GDALApplyGeoTransform(
      geo_normalize,
      pasGCPs[i].dfGCPX,
      pasGCPs[i].dfGCPY
    )

    sum_x += pixel
    sum_y += line
    sum_xy += pixel * line
    sum_xx += pixel * pixel
    sum_yy += line * line
    sum_Lon += geox
    sum_Lonx += geox * pixel
    sum_Lony += geox * line
    sum_Lat += geoy
    sum_Latx += geoy * pixel
    sum_Laty += geoy * line
  }

  const divisor =
    nGCPCount * (sum_xx * sum_yy - sum_xy * sum_xy) +
    2 * sum_x * sum_y * sum_xy -
    sum_y * sum_y * sum_xx -
    sum_x * sum_x * sum_yy

  /* -------------------------------------------------------------------- */
  /*      If the divisor is zero, there is no valid solution.             */
  /* -------------------------------------------------------------------- */
  if (divisor === 0.0) return false

  /* -------------------------------------------------------------------- */
  /*      Compute top/left origin.                                        */
  /* -------------------------------------------------------------------- */
  const gt_normalized = new Float64Array(6)
  gt_normalized[0] =
    (sum_Lon * (sum_xx * sum_yy - sum_xy * sum_xy) +
      sum_Lonx * (sum_y * sum_xy - sum_x * sum_yy) +
      sum_Lony * (sum_x * sum_xy - sum_y * sum_xx)) /
    divisor

  gt_normalized[3] =
    (sum_Lat * (sum_xx * sum_yy - sum_xy * sum_xy) +
      sum_Latx * (sum_y * sum_xy - sum_x * sum_yy) +
      sum_Laty * (sum_x * sum_xy - sum_y * sum_xx)) /
    divisor

  /* -------------------------------------------------------------------- */
  /*      Compute X related coefficients.                                 */
  /* -------------------------------------------------------------------- */
  gt_normalized[1] =
    (sum_Lon * (sum_y * sum_xy - sum_x * sum_yy) +
      sum_Lonx * (nGCPCount * sum_yy - sum_y * sum_y) +
      sum_Lony * (sum_x * sum_y - sum_xy * nGCPCount)) /
    divisor

  gt_normalized[2] =
    (sum_Lon * (sum_x * sum_xy - sum_y * sum_xx) +
      sum_Lonx * (sum_x * sum_y - nGCPCount * sum_xy) +
      sum_Lony * (nGCPCount * sum_xx - sum_x * sum_x)) /
    divisor

  /* -------------------------------------------------------------------- */
  /*      Compute Y related coefficients.                                 */
  /* -------------------------------------------------------------------- */
  gt_normalized[4] =
    (sum_Lat * (sum_y * sum_xy - sum_x * sum_yy) +
      sum_Latx * (nGCPCount * sum_yy - sum_y * sum_y) +
      sum_Laty * (sum_x * sum_y - sum_xy * nGCPCount)) /
    divisor

  gt_normalized[5] =
    (sum_Lat * (sum_x * sum_xy - sum_y * sum_xx) +
      sum_Latx * (sum_x * sum_y - nGCPCount * sum_xy) +
      sum_Laty * (nGCPCount * sum_xx - sum_x * sum_x)) /
    divisor
  /* -------------------------------------------------------------------- */
  /*      Compose the resulting transformation with the normalization     */
  /*      geotransformations.                                             */
  /* -------------------------------------------------------------------- */
  const gt1p2 = new Float64Array(6)
  const inv_geo_normalize = new Float64Array(6)
  if (!GDALInvGeoTransform(geo_normalize, inv_geo_normalize)) return false

  GDALComposeGeoTransforms(pl_normalize, gt_normalized, gt1p2)

  GDALComposeGeoTransforms(gt1p2, inv_geo_normalize, padfGeoTransform)

  /* -------------------------------------------------------------------- */
  /*      Now check if any of the input points fit this poorly.           */
  /* -------------------------------------------------------------------- */
  if (bApproxOK) {
    // FIXME? Not sure if it is the more accurate way of computing
    // pixel size
    const dfPixelSize =
      0.5 *
      (Math.abs(padfGeoTransform[1]) +
        Math.abs(padfGeoTransform[2]) +
        Math.abs(padfGeoTransform[4]) +
        Math.abs(padfGeoTransform[5]))
    if (dfPixelSize === 0.0) {
      Log.info('dfPixelSize = 0')
      return false
    }

    for (let i = 0; i < nGCPCount; i++) {
      const dfErrorX =
        pasGCPs[i].dfGCPPixel * padfGeoTransform[1] +
        pasGCPs[i].dfGCPLine * padfGeoTransform[2] +
        padfGeoTransform[0] -
        pasGCPs[i].dfGCPX

      const dfErrorY =
        pasGCPs[i].dfGCPPixel * padfGeoTransform[4] +
        pasGCPs[i].dfGCPLine * padfGeoTransform[5] +
        padfGeoTransform[3] -
        pasGCPs[i].dfGCPY

      if (
        Math.abs(dfErrorX) > dfPixelThreshold * dfPixelSize ||
        Math.abs(dfErrorY) > dfPixelThreshold * dfPixelSize
      ) {
        Log.info(
          `dfErrorX/dfPixelSize = ${
            Math.abs(dfErrorX) / dfPixelSize
          }, dfErrorY/dfPixelSize = ${Math.abs(dfErrorY) / dfPixelSize}`
        )
        return false
      }
    }
  }
  return true
}
构造函数
成员变量
方法
事件