管道基础大数据平台系统开发-【前端】-新系統界面
编辑 | blame | 历史 | 原始文档
/**
 *
 *  脱离Cesium球体进行各种坐标转换
 *
 *     有个人理解修改,注释说明个人理解
 *
 *             金磊
 *
 */

/**
 *  myScene格式及赋值
 *   myScene = {
 *          width:scene.canvas.clientWidth,
 *          height:scene.canvas.clientHeight,
 *          viewMatrix:scene.camera.viewMatrix,//([16])
 *          projectionMatrix:scene.camera.frustum.projectionMatrix,//([16])
 *          frustum:{
 *                   fovy:viewer.scene.camera.frustum.fovy,
 *                   aspectRatio:viewer.scene.camera.frustum.aspectRatio,
 *                   near:viewer.scene.camera.frustum.near,
 *                  },
 *          positionWC:viewer.scene.camera.positionWC,
 *          directionWC:viewer.scene.camera.directionWC,
 *          rightWC:viewer.scene.camera.rightWC,
 *          upWC:viewer.scene.camera.upWC
 *         }
 */

//球体参数(4490坐标系)

var Ellipsoid = {
    radii: {
        //椭球体半径
        x: 6378137.0,
        y: 6378137.0,
        z: 6356752.31414035585,
    },
    radiiSquared: {
        //椭球体半径平方
        x: 40680631590769.0,
        y: 40680631590769.0,
        z: 40408299983328.77,
    },
    oneOverRadii: {
        x: 1.0 / 6378137.0,
        y: 1.0 / 6378137.0,
        z: 1.0 / 6356752.31414035585,
    },
    oneOverRadiiSquared: {
        x: 1.0 / 40680631590769.0,
        y: 1.0 / 40680631590769.0,
        z: 1.0 / 40408299983328.77,
    },
    centerToleranceSquared: 0.1,
};
var EPSILON12 = 0.000000000001;
//屏幕坐标转经纬度
function rayEllipsoid(ray) {
    var inverseRadii = Ellipsoid.oneOverRadii;
    var q = multiplyComponents(inverseRadii, ray.origin);
    var w = multiplyComponents(inverseRadii, ray.direction);

    var q2 = q.x * q.x + q.y * q.y + q.z * q.z;
    var qw = cartesianDot(q, w);

    var difference, w2, product, discriminant, temp;

    if (q2 > 1.0) {
        // Outside ellipsoid.
        if (qw >= 0.0) {
            // Looking outward or tangent (0 intersections).
            return undefined;
        }

        // qw < 0.0.
        var qw2 = qw * qw;
        difference = q2 - 1.0; // Positively valued.
        w2 = w.x * w.x + w.y * w.y + w.z * w.z;
        product = w2 * difference;

        if (qw2 < product) {
            // Imaginary roots (0 intersections).
            return undefined;
        } else if (qw2 > product) {
            // Distinct roots (2 intersections).
            discriminant = qw * qw - product;
            temp = -qw + Math.sqrt(discriminant); // Avoid cancellation.
            var root0 = temp / w2;
            var root1 = difference / temp;
            if (root0 < root1) {
                return { start: root0, stop: root1 };
            }

            return {
                start: root1,
                stop: root0,
            };
        }
        // qw2 == product.  Repeated roots (2 intersections).
        var root = Math.sqrt(difference / w2);
        return { start: root, stop: root };
    } else if (q2 < 1.0) {
        // Inside ellipsoid (2 intersections).
        difference = q2 - 1.0; // Negatively valued.
        w2 = w.x * w.x + w.y * w.y + w.z * w.z;
        product = w2 * difference; // Negatively valued.

        discriminant = qw * qw - product;
        temp = -qw + Math.sqrt(discriminant); // Positively valued.
        return { start: 0.0, stop: temp / w2 };
    }
    // q2 == 1.0. On ellipsoid.
    if (qw < 0.0) {
        // Looking inward.
        w2 = w.x * w.x + w.y * w.y + w.z * w.z;
        return { start: 0.0, stop: -qw / w2 };
    }

    // qw >= 0.0.  Looking outward or tangent.
    return undefined;
}
//笛卡尔坐标相加
function cartesianAdd(left, right) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };
    result.x = left.x + right.x;
    result.y = left.y + right.y;
    result.z = left.z + right.z;
    return result;
}
//笛卡尔坐标相减
function subtract(left, right) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };
    result.x = left.x - right.x;
    result.y = left.y - right.y;
    result.z = left.z - right.z;
    return result;
}
function magnitude(cartesian) {
    return Math.sqrt(
        cartesian.x * cartesian.x +
            cartesian.y * cartesian.y +
            cartesian.z * cartesian.z
    );
}
//获得webgl透视图
function getPickRayPerspective(scene, windowPosition) {
    var result = {
        origin: { x: 0.0, y: 0.0, z: 0.0 },
        direction: { x: 0.0, y: 0.0, z: 0.0 },
    };

    var width = scene.width;
    var height = scene.height;

    var tanPhi = Math.tan(scene.frustum.fovy * 0.5);

    var tanTheta = scene.frustum.aspectRatio * tanPhi;
    var near = scene.frustum.near;

    //将坐标缩放到-1~1内
    var x = (2.0 / width) * windowPosition.x - 1.0;
    var y = (2.0 / height) * (height - windowPosition.y) - 1.0;
    
    var position = scene.positionWC;
    result.origin.x = position.x;
    result.origin.y = position.y;
    result.origin.z = position.z;
    //获取near中心点相对相机的位置
    var nearCenter = multiplyByScalar(scene.directionWC, near);
    //获取near中心点在webgl场景绝对位置
    nearCenter = cartesianAdd(position, nearCenter);
    
    var xDir = multiplyByScalar(scene.rightWC, x * near * tanTheta);
    var yDir = multiplyByScalar(scene.upWC, y * near * tanPhi);
    //计算传入射线与near平面交点在webgl场景内的绝对位置
    result.direction = cartesianAdd(nearCenter, xDir);
    result.direction = cartesianAdd(result.direction, yDir);
    //计算传入射线与near平面交点与相机的相对位置
    result.direction = subtract(result.direction, position);
    //除以与相机连线长度,计算出三个角度的tan值
    result.direction = normalize(result.direction);

    return result;
}
function getPoint(ray, t) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };
    result = multiplyByScalar(ray.direction, t);
    result = cartesianAdd(ray.origin, result);
    return result;
}
/**
 *
 *  scene = {
 *          width:scene.canvas.clientWidth,
 *          height:scene.canvas.clientHeight,
 *          frustum:{
 *                   fovy:viewer.scene.camera.frustum.fovy,
 *                   aspectRatio:viewer.scene.camera.frustum.aspectRatio,
 *                   near:viewer.scene.camera.frustum.near,
 *                  },
 *          positionWC:viewer.scene.camera.positionWC,
 *          directionWC:viewer.scene.camera.directionWC,
 *          rightWC:viewer.scene.camera.rightWC,
 *          upWC:viewer.scene.camera.upWC
 *          }
 *
 *
 */
function pickEllipsoid(scene, windowPosition) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    //获得获取屏幕像素在webgl内射线的透视图。
    var ray = getPickRayPerspective(scene, windowPosition);
    //计算射线与球的交点
    var intersection = rayEllipsoid(ray);
    if (!intersection) {
        return undefined;
    }
    var t = intersection.start > 0.0 ? intersection.start : intersection.stop;
    result = getPoint(ray, t);
    return result;
}
function scaleToGeodeticSurface(cartesian) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };

    var oneOverRadii = Ellipsoid.oneOverRadii;
    var oneOverRadiiSquared = Ellipsoid.oneOverRadiiSquared;
    var centerToleranceSquared = Ellipsoid._centerToleranceSquared;

    var positionX = cartesian.x;
    var positionY = cartesian.y;
    var positionZ = cartesian.z;

    var oneOverRadiiX = oneOverRadii.x;
    var oneOverRadiiY = oneOverRadii.y;
    var oneOverRadiiZ = oneOverRadii.z;

    var x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX;
    var y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY;
    var z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ;

    // Compute the squared ellipsoid norm.
    var squaredNorm = x2 + y2 + z2;
    var ratio = Math.sqrt(1.0 / squaredNorm);

    // As an initial approximation, assume that the radial intersection is the projection point.
    var intersection = multiplyByScalar(cartesian, ratio);

    // If the position is near the center, the iteration will not converge.
    if (squaredNorm < centerToleranceSquared) {
        if (!isFinite(ratio)) {
            return undefined;
        } else {
            result.x = intersection.x;
            result.y = intersection.y;
            result.z = intersection.z;
            return result;
        }
    }

    var oneOverRadiiSquaredX = oneOverRadiiSquared.x;
    var oneOverRadiiSquaredY = oneOverRadiiSquared.y;
    var oneOverRadiiSquaredZ = oneOverRadiiSquared.z;

    // Use the gradient at the intersection point in place of the true unit normal.
    // The difference in magnitude will be absorbed in the multiplier.
    var gradient = { x: 0.0, y: 0.0, z: 0.0 };
    gradient.x = intersection.x * oneOverRadiiSquaredX * 2.0;
    gradient.y = intersection.y * oneOverRadiiSquaredY * 2.0;
    gradient.z = intersection.z * oneOverRadiiSquaredZ * 2.0;

    // Compute the initial guess at the normal vector multiplier, lambda.
    var lambda =
        ((1.0 - ratio) * magnitude(cartesian)) / (0.5 * magnitude(gradient));
    var correction = 0.0;

    var func;
    var denominator;
    var xMultiplier;
    var yMultiplier;
    var zMultiplier;
    var xMultiplier2;
    var yMultiplier2;
    var zMultiplier2;
    var xMultiplier3;
    var yMultiplier3;
    var zMultiplier3;

    do {
        lambda -= correction;

        xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX);
        yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY);
        zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ);

        xMultiplier2 = xMultiplier * xMultiplier;
        yMultiplier2 = yMultiplier * yMultiplier;
        zMultiplier2 = zMultiplier * zMultiplier;

        xMultiplier3 = xMultiplier2 * xMultiplier;
        yMultiplier3 = yMultiplier2 * yMultiplier;
        zMultiplier3 = zMultiplier2 * zMultiplier;

        func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0;

        // "denominator" here refers to the use of this expression in the velocity and acceleration
        // computations in the sections to follow.
        denominator =
            x2 * xMultiplier3 * oneOverRadiiSquaredX +
            y2 * yMultiplier3 * oneOverRadiiSquaredY +
            z2 * zMultiplier3 * oneOverRadiiSquaredZ;

        var derivative = -2.0 * denominator;

        correction = func / derivative;
    } while (Math.abs(func) > EPSILON12);

    result.x = positionX * xMultiplier;
    result.y = positionY * yMultiplier;
    result.z = positionZ * zMultiplier;
    return result;
}
function cartesianToDegrees(cartesian) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };
    var oneOverRadiiSquared = Ellipsoid.oneOverRadiiSquared;
    var p = scaleToGeodeticSurface(cartesian);

    if (p == undefined) {
        return undefined;
    }

    var n = multiplyComponents(p,oneOverRadiiSquared);
    n = normalize(n);

    var longitude = Math.atan2(n.y, n.x);
    var latitude = Math.asin(n.z);

    result.x = longitude;
    result.y = latitude;
    return result;
}
/**
 * 根据屏幕像素取经纬度
 * @param {*} x
 * @param {*} y
 */
function tempgetCoord(x, y) {
    //像素位置转迪卡尔坐标
    var car3_lc = pickEllipsoid(myScene, { x: x, y: y });
    if (car3_lc) {
        //迪卡尔坐标转弧度
        var cartographicLC = cartesianToDegrees(car3_lc);
        //转经纬度
        var point = toDegrees([
            cartographicLC.x,
            cartographicLC.y,
        ]);
        return [point.x, point.y];
    } else {
        return null;
    }
}

//经纬度转屏幕坐标

//弧度/度
var RADIANS_PER_DEGREE = Math.PI / 180.0;
//度/弧度
var DEGREES_PER_RADIAN = 180.0 / Math.PI;
//弧度转笛卡尔坐标与半径的比例
/**
 *      先计算cos(lat),结果没意义为了化简
 *      P/R = cos(lat);
 *      cos(lat) * cos(lon) = P/R * x/P = x/R
 *      cos(lat) * sin(lon) = P/R * y/P = y/R
 *      sin(lat) = z/R
 */
function geodeticSurfaceNormalCartographic(cartographic) {
    var longitude = cartographic.x;
    var latitude = cartographic.y;

    var cosLatitude = Math.cos(latitude);
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = cosLatitude * Math.cos(longitude);
    result.y = cosLatitude * Math.sin(longitude);
    result.z = Math.sin(latitude);
    return result;
}
//球体笛卡尔与坐标笛卡尔乘积
function multiplyComponents(left, right) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = left.x * right.x;
    result.y = left.y * right.y;
    result.z = left.z * right.z;

    return result;
}
function toDegrees(point) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = point[0] * DEGREES_PER_RADIAN;
    result.y = point[1] * DEGREES_PER_RADIAN;
    return result;
}
//经纬度转弧度
function toRadians(point) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = point[0] * RADIANS_PER_DEGREE;
    result.y = point[1] * RADIANS_PER_DEGREE;
    return result;
}
//计算两个笛卡尔坐标的(标量)乘积
function cartesianDot(left, right) {
    return left.x * right.x + left.y * right.y + left.z * right.z;
}
function divideByScalar(cartesian, scalar) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = cartesian.x / scalar;
    result.y = cartesian.y / scalar;
    result.z = cartesian.z / scalar;
    return result;
}
//矢量成标量
function multiplyByScalar(cartesian, scalar) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    result.x = cartesian.x * scalar;
    result.y = cartesian.y * scalar;
    result.z = cartesian.z * scalar;
    return result;
}
//经纬度转笛卡尔坐标
/**
 *      有误差,和Cesium计算方法一致,误差都一样相对位置应该没什么问题
 *      先使用了“Haversine公式”方法,最大误差应该为0.4%,
 *      然后使用有误差的值,去纠正另一个有误差的值,感觉这个思路的逻辑不太好,但应该会缩小误差。
 * */
function degreesToCartesian(point) {
    //转换坐标为弧度坐标
    var cartographic = toRadians(point);
    //弧度坐标转换为笛卡尔坐标(0~1)
    var posCartesian = geodeticSurfaceNormalCartographic(cartographic);
    /**
     *  我的理解2000坐标系是椭球,实际是有误差,最大误差为0.4%,不知道是不是理解错误
     *  实际意义不大,为了后面化简
     *  xR = (x / R) * R * R
     *  yR = (y / R) * R * R
     *  zR = (z / R) * R * R
     *  有误差,实际值  rr < R < r
     *  xR ~= (x / R) * r * r = x*r*r/R
     *  yR ~= (y / R) * r * r = y*r*r/R
     *  zR ~= (z / R) * rr * rr = z*rr*rr/R
     */
    var eldCartesian = multiplyComponents(Ellipsoid.radiiSquared, posCartesian);
    /**
     *      计算点距离球心距离,依然是上面误差的问题
     *       _______________________________
     *  d = √ xR * x/R + yR * y/R + zR * z/R
     *       _________________
     *    = √ x*x + y*y + z*z
     *
     *      实际计算公式  rr < R < r
     *       ________________________________________________
     *  d = √ x*r*r/R * x/R + y*r*r/R * y/R + z*rr*rr/R * z/R
     *       ________________________________________________
     *    = √ x*x * r*r/R*R + y*y * r*r/R*R + z*z * rr*rr/R*R
     *       ____________________________________
     *      √ x*x * r*r + y*y * r*r + z*z * rr*rr
     *    = -------------------------------------
     *                     R
     *
     */
    var gamma = Math.sqrt(cartesianDot(posCartesian, eldCartesian));
    /**
     *  猜测值,算是缩小误差  rr < R < r
     *  R ~= d
     *  xR / d = x
     *  yR / d = y
     *  zR / d = z
     *
     *
     *  实际值
     *                      ____________________________________
     *  x = (x*r*r/R) * (R/√ x*x * r*r + y*y * r*r + z*z * rr*rr)
     *             ____________________________________
     *    = x*r*r/√ x*x * r*r + y*y * r*r + z*z * rr*rr
     *                      ____________________________________
     *  y = (y*r*r/R) * (R/√ x*x * r*r + y*y * r*r + z*z * rr*rr)
     *             ____________________________________
     *    = y*r*r/√ x*x * r*r + y*y * r*r + z*z * rr*rr
     *                        ____________________________________
     *  z = (z*rr*rr/R) * (R/√ x*x * r*r + y*y * r*r + z*z * rr*rr)
     *               ____________________________________
     *    = z*rr*rr/√ x*x * r*r + y*y * r*r + z*z * rr*rr
     *
     */
    var divide = divideByScalar(eldCartesian, gamma);
    /**
     *  x/R * h
     *  y/R * h
     *  z/R * h
     */
    var multiply = multiplyByScalar(posCartesian, 0);

    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    //Cartesian3.add(divide, multiply, result);因为高度是0,所以可以不加
    return divide;
}
function multiplyByVector(matrix, cartesian) {
    var result = { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
    var vX = cartesian.x;
    var vY = cartesian.y;
    var vZ = cartesian.z;
    var vW = cartesian.w;

    var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12] * vW;
    var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13] * vW;
    var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14] * vW;
    var w = matrix[3] * vX + matrix[7] * vY + matrix[11] * vZ + matrix[15] * vW;

    result.x = x;
    result.y = y;
    result.z = z;
    result.w = w;
    return result;
}
function fromElements(x, y, z, w) {
    var result = { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
    result.x = x;
    result.y = y;
    result.z = z;
    result.w = w;
    return result;
}
function normalize(cartesian) {
    var result = {
        x: 0.0,
        y: 0.0,
        z: 0.0,
    };
    var magnitude = Math.sqrt(
        cartesian.x * cartesian.x +
            cartesian.y * cartesian.y +
            cartesian.z * cartesian.z
    );

    result.x = cartesian.x / magnitude;
    result.y = cartesian.y / magnitude;
    result.z = cartesian.z / magnitude;

    return result;
}
function worldToClip(position, viewMatrix, projectionMatrix) {
    var position4 = fromElements(position.x, position.y, position.z, 1);
    var positionEC = multiplyByVector(viewMatrix, position4);
    return multiplyByVector(projectionMatrix, positionEC);
}
function computeViewportTransformation(
    viewport,
    nearDepthRange,
    farDepthRange
) {
    var result = [];
    var x = viewport.x;
    var y = viewport.y;
    var width = viewport.width;
    var height = viewport.height;

    var halfWidth = width * 0.5;
    var halfHeight = height * 0.5;
    var halfDepth = (farDepthRange - nearDepthRange) * 0.5;

    var column0Row0 = halfWidth;
    var column1Row1 = halfHeight;
    var column2Row2 = halfDepth;
    var column3Row0 = x + halfWidth;
    var column3Row1 = y + halfHeight;
    var column3Row2 = nearDepthRange + halfDepth;
    var column3Row3 = 1.0;

    result[0] = column0Row0;
    result[1] = 0.0;
    result[2] = 0.0;
    result[3] = 0.0;
    result[4] = 0.0;
    result[5] = column1Row1;
    result[6] = 0.0;
    result[7] = 0.0;
    result[8] = 0.0;
    result[9] = 0.0;
    result[10] = column2Row2;
    result[11] = 0.0;
    result[12] = column3Row0;
    result[13] = column3Row1;
    result[14] = column3Row2;
    result[15] = column3Row3;
    return result;
}
function multiplyByPoint(matrix, cartesian) {
    var result = { x: 0.0, y: 0.0, z: 0.0 };
    var vX = cartesian.x;
    var vY = cartesian.y;
    var vZ = cartesian.z;

    var x = matrix[0] * vX + matrix[4] * vY + matrix[8] * vZ + matrix[12];
    var y = matrix[1] * vX + matrix[5] * vY + matrix[9] * vZ + matrix[13];
    var z = matrix[2] * vX + matrix[6] * vY + matrix[10] * vZ + matrix[14];

    result.x = x;
    result.y = y;
    result.z = z;
    return result;
}
function clipToGLWindowCoordinates(viewport, position) {
    var positionNDC = divideByScalar(position, position.w);

    // Viewport transform to transform from clip coordinates to window coordinates
    var viewportTransform = computeViewportTransformation(viewport, 0.0, 1.0);
    var positionWC = multiplyByPoint(viewportTransform, positionNDC);

    return { x: positionWC.x, y: positionWC.y };
}
var positionCC = { x: 0.0, y: 0.0, z: 0.0, w: 0.0 };
//笛卡尔坐标转屏幕像素
/**
 * scene = {
 *          width:scene.canvas.clientWidth,
 *          height:scene.canvas.clientHeight,
 *          viewMatrix:scene.camera.viewMatrix,([16])
 *          projectionMatrix:scene.camera.frustum.projectionMatrix([16])
 *         }
 *
 *
 * position = {
 *              x:0.0,
 *              y:0.0,
 *              z:0.0
 *             }
 *
 *
 */
function cartesianToWindow(scene, position) {
    var actualPosition = position;
    //获取画布信息
    var viewport = { x: 0.0, y: 0.0, width: 0.0, height: 0.0 };
    viewport.x = 0;
    viewport.y = 0;
    viewport.width = scene.width;
    viewport.height = scene.height;

    var cameraCentered = true;
    var result = { x: 0.0, y: 0.0 };
    //暂时只考虑三维
    if (cameraCentered) {
        // 从世界坐标转换到裁切坐标的投影矩阵
        positionCC = worldToClip(
            actualPosition,
            scene.viewMatrix,
            scene.projectionMatrix
        );
        if (positionCC.z < 0) {
            return undefined;
        }

        result = clipToGLWindowCoordinates(viewport, positionCC);
    }

    result.y = scene.height - result.y;
    return result;
}
function projection(point) {
    //经纬度转笛卡尔坐标
    var cartesian = degreesToCartesian(point);
    //迪卡尔转像素
    var pixel = cartesianToWindow(myScene, cartesian);
    return pixel;
}