经纬度坐标如何转成three.js或者babylon.js中的三维坐标

需求

有如下需求,根据接口返回来的经纬度坐标,要正确的在three.js中的三维场景中将数据渲染出来,或者是根据经纬度坐标绘制出物体的运动轨迹

分析

我们都知道三维three.js的渲染用的是右手笛卡坐标,并没有像cesium那样给我们提供经纬度和笛卡尔坐标的转换方式,转换的时候注意坐标的方向(是否涉及到旋转),缩放坐标原点

那我们可以借助cesium的方法将经纬度转成的卡尔坐标,然后在three.js或者babylon.js进行渲染吗?

答案是可以的,但是存在一个问题,就是坐标轴指向、原点都是不一样的,光靠一个坐标点是没办法定位,还需要坐标系的信息,在这些坐标系中,一旦涉及到旋转,那么难度将以指数的形式增加。

在three.js右手笛卡尔中表示高度是Y值,但是在cesium的笛卡尔是没办法表示高度的,只能是用经纬度表示例如(lon,lat,height),坐标系信息可以看下面的图

cesium WGS84坐标系

cesium笛卡尔坐标系

three.js坐标系

举个例子,比如cesium开发的系统,用的是cesium的笛卡尔坐标系,一辆车朝着某个方向运动,比如朝着正北方向运动,而你的系统中,比如是three.js的系统,你很难根据经纬度或者cesium的笛卡尔坐标信息将两边的运动进行同步,那怎么办呢?不妨看看下面的方法吧

实现步骤如下

步骤一:设置原点,经纬度转墨卡托,平移,墨卡托转three.js的右手笛卡尔

既然如此那我们可以手动将经纬度转成墨卡托;然后将转换后的经度对应成three.js中位置的x,维度对应成three.js中的z值即可,但是用过cesium或者openlayers的兄弟都应该知道,经过转换的坐标,数值上是非常大的,这对我们的无论是计算还是啥的都不方便,我们可以先指定一个坐标原点,接下里输入的坐标,都以这个原点定位,相当于做了一个平移,这个才坐标转换是常用的手段

步骤二:正确缩放

在坐标轴指向和尺度上来看,首先是指向,当时向着正东方向运动时经纬度的经度会增加,此时对于的three.js中的x会增加,维度亦是如此

重点:WGS84坐标经纬度转成墨卡托,再放到三维场景定位存在一定的缩放,缩放系数要根据场景的大小,如何确定呢,比如在一些卫星底图下载软件,你要下载直径5公里的底图,但是发现得到的是大于5公里的,那缩放系数就是实际得到的直径出于输入的直径,当我们要一比一还原实际场景的时候,建模时也要按照这个系数进行缩放;

接下来是代码实现,代码每个方法的命名就是注释,应该挺好看懂的,分别有两个版本,一个是通过手动将经纬度转墨卡托(伪墨卡托3857),另一个是通过proj4

步骤三:旋转

这里不涉及到旋转,跳过

proj4版本

class GeoCoordinateConverter {
  [x: string]: any;
  offsetX: any;
  offsetZ: any;
  source: any;
  destination: any;
  constructor(latitude:any, longitude:any) {
    // 定义投影坐标系,这里以WGS84为例
    proj4.defs("WGS84", "+proj=longlat +datum=WGS84 +no_defs");
    this.source = proj4.Proj("WGS84");
    this.destination = proj4.Proj(
      "+proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m +nadgrids=@null +wktext +no_defs"
    );

    this.mercatorOrigin = proj4.transform(this.source, this.destination, [
      longitude,
      latitude,
    ]);
    this.offsetX = -this.mercatorOrigin.x;
    this.offsetZ = -this.mercatorOrigin.y;
  }

  convertLatLngToThreeJS(latitude:any, longitude:any) {
    var mercatorCoordinates = proj4.transform(this.source, this.destination, [
      longitude,
      latitude,
    ]);

    const x = (mercatorCoordinates.x + this.offsetX) / 1.0870778616;
    const z = (mercatorCoordinates.y + this.offsetZ) / 1.0870778616;
    return { x: x, y: 0, z: z };
  }
}

// 使用示例
var converter = new GeoCoordinateConverter(23.5684, 111.3565);
converter.convertLatLngToThreeJS(23.569, 111.357);
converter.convertLatLngToThreeJS(23.5699, 111.357);

手动计算版本

// 使用示例
var converter = new GeoCoordinateConverter(23.5684, 111.3565);
converter.convertLatLngToThreeJS(23.569, 111.357);
converter.convertLatLngToThreeJS(23.5699, 111.357);

class GeoCoordinateConverterT {
  private longitude: number;
  private latitude: number;
  private mercatorOrigin: [number, number];
  private offsetX: number;
  private offsetZ: number;

  constructor(longitude: number, latitude: number) {
    this.longitude = longitude;
    this.latitude = latitude;
    this.mercatorOrigin = this.latLngToWebMercator(longitude, latitude);
    this.offsetX = -this.mercatorOrigin[0];
    this.offsetZ = -this.mercatorOrigin[1];
  }

  private latLngToWebMercator(longitude: number, latitude: number): [number, number] {
    const earthRad = 6378137.0;
    const x = (longitude * Math.PI / 180) * earthRad;
    const a = (latitude * Math.PI / 180);
    const y = (earthRad / 2) * Math.log((1.0 + Math.sin(a)) / (1.0 - Math.sin(a)));
    return [x, y];
  }

  public convertLatLngToThreeJS(longitude: number, latitude: number): { x: number, y: number, z: number } {
    const mercatorCoordinates = this.latLngToWebMercator(longitude, latitude);
    const x = (mercatorCoordinates[0] + this.offsetX) / 1.0870778616;
    const z = (mercatorCoordinates[1] + this.offsetZ) / 1.0870778616;
    return { x, y: 0, z };
  }

  public convertThreeJSToLatLng(x: number, z: number): { longitude: number, latitude: number } {
    const inverseX = x - this.offsetX;
    const inverseZ = z - this.offsetZ;

    const earthRad = 6378137.0;
    const latitude = (Math.atan(Math.exp(inverseZ / earthRad)) * 360 / Math.PI) - 90;
    const longitude = (inverseX / (earthRad * Math.PI / 180));

    return { longitude, latitude };
  }
}

// 使用示例
const convertert = new GeoCoordinateConverterT(111.3565, 23.5684);
const threeJsCoordinates = convertert.convertLatLngToThreeJS(111.3579, 23.5699);

console.log(threeJsCoordinates);

const latLng = convertert.convertThreeJSToLatLng(500,500);

console.log(latLng);

经过计算两个版本误差都不大,小于3米,几乎可以通用,也可以手动多选几个点多不同的方向象限的值对缩放系数做不同处理,不如象限的值乘以系数上下浮动0.01啥的,从而提高精度,当一些比较大的场景建议使用proj4

为什么上面说要乘以一个缩放系数,但是我却除以缩放系数呢,因为这样子建模的时候整个场景就不用缩放了

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值