使用Cesium开发三维GIS应用离不开笛卡尔坐标系,在CesiumJS中定义类型是Cartesian3,这是Cesium的基础数据类型,所有坐标最后均转换成这个类型参与三维渲染,包括屏幕坐标,地理坐标系坐标。那么问题来了,这个笛卡尔坐标系到底是什么鬼?常用的WGS84怎么转换成这个坐标系的?让我们来看看cesium源码一探究竟。
Cartesian3.js里面有个函数fromRadians ,将经纬度转换成Cartesian3,其中经纬度是wgs84转换成弧度的经纬度。
Cartesian3.fromRadians = function(longitude, latitude, height, ellipsoid, result) {
//>>includeStart('debug', pragmas.debug);
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
//>>includeEnd('debug');
height = defaultValue(height, 0.0);
var radiiSquared = defined(ellipsoid) ? ellipsoid.radiiSquared : wgs84RadiiSquared;
var cosLatitude = Math.cos(latitude);
scratchN.x = cosLatitude * Math.cos(longitude);
scratchN.y = cosLatitude * Math.sin(longitude);
scratchN.z = Math.sin(latitude);
scratchN = Cartesian3.normalize(scratchN, scratchN);
Cartesian3.multiplyComponents(radiiSquared, scratchN, scratchK);
var gamma = Math.sqrt(Cartesian3.dot(scratchN, scratchK));
scratchK = Cartesian3.divideByScalar(scratchK, gamma, scratchK);
scratchN = Cartesian3.multiplyByScalar(scratchN, height, scratchN);
if (!defined(result)) {
result = new Cartesian3();
}
return Cartesian3.add(scratchK, scratchN, result);
};
Check.typeOf.number('longitude', longitude);
Check.typeOf.number('latitude', latitude);
height = defaultValue(height, 0.0);
var radiiSquared = defined(ellipsoid) ? ellipsoid.radiiSquared : wgs84RadiiSquared;
var wgs84RadiiSquared = new Cartesian3(6378137.0 * 6378137.0, 6378137.0 * 6378137.0, 6356752.3142451793 * 6356752.3142451793);
var cosLatitude = Math.cos(latitude);
所以这句话的意思是假设球半径为1,这个半径投影到xy平面上的长度(当然由于这不是标准球,所以球半径不是固定的,但是在这里假设是一个标准球,直到下面第9步骤)。
scratchN.x = cosLatitude * Math.cos(longitude);
scratchN.y = cosLatitude * Math.sin(longitude);
scratchN.z = Math.sin(latitude);
scratchN = Cartesian3.normalize(scratchN, scratchN);
Cartesian3.multiplyComponents(radiiSquared, scratchN, scratchK)
var gamma = Math.sqrt(Cartesian3.dot(scratchN, scratchK))
该语句执行结果:
g a m m a = N X 2 ∗ R X 2 + N Y 2 ∗ R Y 2 + N Z 2 ∗ R Z 2 gamma=\sqrt{N_X^2*R_X^2+N_Y^2*R_Y^2+N_Z^2*R_Z^2} gamma=NX2∗RX2+NY2∗RY2+NZ2∗RZ2
scratchK = Cartesian3.divideByScalar(scratchK, gamma, scratchK);
该语句执行结果:
scratchK向量 N K = [ N X ∗ R X 2 / g a m m a , N Y ∗ R Y 2 / g a m m a , N Y ∗ R X 2 / g a m m a ] N_K=[N_X*R_X^2/gamma,N_Y*R_Y^2/gamma,N_Y*R_X^2/gamma] NK=[NX∗RX2/gamma,NY∗RY2/gamma,NY∗RX2/gamma]
其实就是: [ ( N X ∗ R X / g a m m a ) ∗ R X , ( N Y ∗ R Y / g a m m a ) ∗ R Y , ( N Z ∗ R Z / g a m m a ) ∗ R Z ] [(N_X*R_X/gamma)*R_X,(N_Y*R_Y/gamma)*R_Y,(N_Z*R_Z/gamma)*R_Z] [(NX∗RX/gamma)∗RX,(NY∗RY/gamma)∗RY,(NZ∗RZ/gamma)∗RZ]
也就是 [ N X ∗ R X , N Y ∗ R Y , N Z ∗ R Z ] [N_X*R_X,N_Y*R_Y,N_Z*R_Z] [NX∗RX,NY∗RY,NZ∗RZ]的模向量 [ N X ∗ R X / g a m m a , N Y ∗ R Y / g a m m a , N Z ∗ R Z / g a m m a ] [N_X*R_X/gamma,N_Y*R_Y/gamma,N_Z*R_Z/gamma] [NX∗RX/gamma,NY∗RY/gamma,NZ∗RZ/gamma]再乘以各个轴对应的放大倍数。
var gamma = Math.sqrt(Cartesian3.dot(scratchN, scratchK));
scratchK = Cartesian3.divideByScalar(scratchK, gamma, scratchK);
回头看到这里的步骤,其实cesium做了两次取模运算,第一次目的是算出经纬度对应的标准球体上面xyz比例,第二次是算出在wgs84坐标系下面进行椭球体拉伸后的xyz比例,最后再用这个拉伸后的比例乘以实际值(以米为单位)算出实际xyz坐标。
scratchN = Cartesian3.multiplyByScalar(scratchN, height, scratchN)
增量H向量= [ h e i g h t ∗ N X , h e i g h t ∗ N Y , h e i g h t ∗ N Z ] [height*N_X,height*N_Y,height*N_Z] [height∗NX,height∗NY,height∗NZ]之所以不用第二次拉伸,个人认为是height跟地球半径相比很小,差别可以忽略不计。
如果进行第二次拉伸此处应该是:
增量H向量= [ h e i g h t ∗ ( N X ∗ R X / g a m m a ) , h e i g h t ∗ ( N Y ∗ R Y / g a m m a ) , h e i g h t ∗ ( N Z ∗ R Z / g a m m a ) ] [height*(N_X*R_X/gamma),height*(N_Y*R_Y/gamma),height*(N_Z*R_Z/gamma)] [height∗(NX∗RX/gamma),height∗(NY∗RY/gamma),height∗(NZ∗RZ/gamma)]
12.将第十步骤和第十一步骤对应的坐标相加得到最终xyz值。
return Cartesian3.add(scratchK, scratchN, result)