/*
 * @Author: cbwu 504-wuchengbo@htsdfp.com
 * @Date: 2024-03-22 15:42:49
 * @LastEditors: cbwu
 * @LastEditTime: 2024-04-13 10:49:17
 * @Description: 地理计算
 */
import {
  Cartesian3,
  Cartographic,
  EllipsoidGeodesic,
  Math as Cesium_Math,
  Matrix4,
  Transforms,
} from 'cesium'
/**
 * 计算空间中一点到一条直线的最短距离的交点(在不考虑地球曲率的情况下)
 * @param lineStart 直线的起点:[longitude1, latitude1, height1]
 * @param lineEnd 直线的终点:[longitude2, latitude2, height2]
 * @param point 空间中的点:[[longitude3, latitude3, height3]]
 * @returns 最近的交点
 */
function getClosestPoint(
  lineStart: Cartesian3,
  lineEnd: Cartesian3,
  point: Cartesian3,
): Cartesian3 {
  // 计算直线方向向量
  const lineDirection = new Cartesian3()
  Cartesian3.subtract(lineEnd, lineStart, lineDirection)
  Cartesian3.normalize(lineDirection, lineDirection)

  // 计算点到直线起点的向量
  const pointToStart = new Cartesian3()
  Cartesian3.subtract(point, lineStart, pointToStart)

  // 计算投影长度,即点到直线的向量在直线方向向量上的分量长度
  const projectionLength = Cartesian3.dot(pointToStart, lineDirection)

  // 使用标量乘法和向量加法确定交点
  const closestPoint = new Cartesian3()
  Cartesian3.multiplyByScalar(lineDirection, projectionLength, closestPoint)
  Cartesian3.add(lineStart, closestPoint, closestPoint)
  return closestPoint
}

/**
 * 判断一个点是否在直线上(在不考虑地球曲率的情况下)
 * @param lineStart 直线的起点:[longitude1, latitude1, height1]
 * @param lineEnd 直线的起点:[longitude2, latitude2, height2]
 * @param pointToCheck 直线的起点:[longitude3, latitude3, height3]
 * @param tolerance 容差
 * @returns 是否在线段上
 */
function isOnLineSegment(
  lineStart: Cartesian3,
  lineEnd: Cartesian3,
  pointToCheck: Cartesian3,
  tolerance: number = Cesium_Math.EPSILON1,
): boolean {
  const dist_AP = Cartesian3.distance(lineStart, pointToCheck)
  const dist_BP = Cartesian3.distance(lineEnd, pointToCheck)
  const dist_AB = Cartesian3.distance(lineStart, lineEnd)

  const isCollinear = Math.abs(dist_AP + dist_BP - dist_AB) < tolerance
  return isCollinear
  /*
  const startToEnd = new Cartesian3()
  const startToPoint = new Cartesian3()
  const endToPoint = new Cartesian3()

  // 计算向量
  Cartesian3.subtract(lineEnd, lineStart, startToEnd)
  Cartesian3.subtract(pointToCheck, lineStart, startToPoint)
  Cartesian3.subtract(pointToCheck, lineEnd, endToPoint)

  // 判断共线
  const cross = Cartesian3.cross(startToEnd, startToPoint, new Cartesian3())
  console.log('cross:' + Cartesian3.magnitude(cross).toString())
  // Math.EPSILON6 是一个非常小的值,用来防止浮点数计算的误差
  const isCollinear = Cartesian3.magnitude(cross) < tolerance

  // 判断点是否在线段之间
  let isBetween = false
  if (isCollinear) {
    const dotProduct1 = Cartesian3.dot(startToEnd, startToPoint)
    const dotProduct2 = Cartesian3.dot(startToEnd, endToPoint)
    isBetween = dotProduct1 >= 0 && dotProduct2 <= 0
  }
  return isBetween
  */
}

/**
 * 计算距离,单位米
 * @param p1 起点
 * @param p2 终点
 */
function getDistance(p1: Cartesian3, p2: Cartesian3): number {
  let point1cartographic = Cartographic.fromCartesian(p1)
  let point2cartographic = Cartographic.fromCartesian(p2)
  /**根据经纬度计算出距离**/
  let geodesic = new EllipsoidGeodesic()
  geodesic.setEndPoints(point1cartographic, point2cartographic)
  return geodesic.surfaceDistance / 1000
}

/**
 * 计算方位角,单位度
 * @param p1 起点
 * @param p2 终点
 * @param digits 保留小数位数,默认为保留1位小数
 */
function getAzimuth(p1: Cartesian3, p2: Cartesian3, digits = 1): string {
  // 建立局部坐标系:北为y,东为x,p1为原点
  const localMatrix = Transforms.eastNorthUpToFixedFrame(p1)
  //求世界坐标到局部坐标的变换矩阵
  const worldToLocalMatrix = Matrix4.inverse(localMatrix, new Matrix4())
  //p1在局部坐标系的位置,即局部坐标原点
  const localPosition1 = Matrix4.multiplyByPoint(
    worldToLocalMatrix,
    p1,
    new Cartesian3(),
  )
  //p2在局部坐标系的位置
  const localPosition2 = Matrix4.multiplyByPoint(
    worldToLocalMatrix,
    p2,
    new Cartesian3(),
  )
  //弧度
  const angle = Math.atan2(
    localPosition2.x - localPosition1.x,
    localPosition2.y - localPosition1.y,
  )
  //转为角度
  let theta = angle * (180 / Math.PI)
  theta = theta < 0 ? theta + 360 : theta
  return theta.toFixed(digits)
}

/**
 * 计算多边形面积,单位平方米
 * @param vertexs 多边形顶点数组
 */
function getPolygonArea(vertexs: Cartesian3[]) {
  let area = 0
  for (let i = 0; i < vertexs.length; i++) {
    let j = (i + 1) % vertexs.length
    area += vertexs[i].x * vertexs[j].y
    area -= vertexs[i].y * vertexs[j].x
  }
  area /= 2
  return Math.abs(area)
}

export {
  getClosestPoint,
  isOnLineSegment,
  getDistance,
  getAzimuth,
  getPolygonArea,
}