GIS与WebGL——可视化效果之绘制地球

lxf2023-05-05 18:13:01

一、简介

地理数据与obj、gltf等模型数据亦或是canvas等图形数据在本质上其实并没什么区别,只是因为地理数据是在地球背景下地图数据,坐标投影系统是以真实地球为基准,地理数据坐标具有特殊的单位,经纬度坐标单位是度,墨卡托坐标单位是米,因此,地理数据被赋予了特殊的含义。

在web可视化中,按照真实比例绘制地球或区域地图显然是不合适的,因此,将地理数据合理的转换为场景数据是必要的。下面介绍一下坐标转换。

 

二、坐标系介绍

1、经纬度

经纬度坐标具体含义和由来这里不细说,地球坐标。在应用时,请注意最重要的一点:在一张世界地图上,左上角点坐标是(-180, 90),右下角点坐标是(180, -90)。

2、墨卡托

墨卡托投影是指等角圆柱投影,能保证形状不变性,投影之后能保证经线纬线各自平行互相垂直。适合做平面地图,Google就是用的墨卡托投影。墨卡托投影单位是米。左上角点坐标为(-20037508.3427892,20037508.3427892)。

3、球极坐标

以坐标原点为参考点,由方位角、仰角、半径构成。空间上的x、y、z可以用这三个参数表示。

三、坐标转换

地理数据坐标系主要分为经纬度和墨卡托,我们往往不能要求这种源数据的坐标信息,所以只能自己处理。

项目采用的方案是:在地球可视化中,采用的经纬度 + 球极坐标。平面地图采用的是墨卡托投影坐标。下面给出坐标转换核心代码

1、经纬度转球极坐标

三角函数即可完成,可以画个图推导一下。

const lonlatToSphere = (lon: number, lat: number, radius: number) => {

  const phi = angleToRad(90 - lat);

  const theta = angleToRad(180 + lon);

  const x = -(radius * Math.sin(phi) * Math.cos(theta));

  const z = radius * Math.sin(phi) * Math.sin(theta);

  const y = radius * Math.cos(phi);

  return { x, y, z };

};

2、经纬度转地球贴图坐标

地球采用threejs中的SphereGeometry,贴图需要按经纬度坐标利用canvas绘制。简单的线性变化,1080、540代表贴图大小。

const lonlatToFlat = (lon: number, lat: number) => {

  const x = ((lon - -180) / 360) * 1080;

  const y = ((90 - lat) / 180) * 540;

  return { x, y };

};

3、经纬度转墨卡托

固定算法。

const lonlatToMocart = (lon: number, lat: number) => {

  const x = (lon * 20037508.34) / 180;

  let y = Math.log(Math.tan(((90 + lat) * Math.PI) / 360)) / (Math.PI / 180);

  y = (y * 20037508.34) / 180;

  return { x, y };

};

4、墨卡托转经纬度

固定算法。

const mocartToLonlat = (x: number, y: number) => {

  const lon = (x / 20037508.34) * 180;

  let lat = (y / 20037508.34) * 180;

  lat = (180 / Math.PI) * (2 * Math.atan(Math.exp((y * Math.PI) / 180)) - Math.PI / 2);

  return { lon, lat };

};

四、绘制地球

源数据为geojson,geojson中记录了世界地图几何形状、几何坐标信息以及几何对应的属性。

生成地球时需要4张贴图:

利用canvas API 以及坐标转换绘制一张地球国家轮廓线、一张地球国家行政区划和一张海洋深度图。可以将绘制结果保存为png文件,这样可以避免每次都绘制一遍,造成不必要的损耗。绘制区划时,注意将每个几何颜色赋值为(1, 1, 1)、(2, 2, 2)、(3, 3, 3),依次类推。海洋设置为0。

利用canvas绘制一个255×1的索引图,第0个像素设置为(0, 0, 0)代表海洋,其余设置为(1, 1, 1),代表对应的国家。

使用THREE.SphereGeometry方法生成一个圆球当作地球,使用THREE.ShaderMaterial生成材质,ShaderMaterial需要自己写着色器代码实现,这里列出着色器的代码。

片元着色器

  uniform sampler2D mapIndex;
  uniform sampler2D lookup;
  uniform sampler2D outline;
  uniform sampler2D depthTexture; 
  uniform vec3 surfaceColor;
  uniform vec3 lineColor;
  uniform vec3 oceanColor;
  varying vec2 vUv;

  void main() {
    vec4 mapColor = texture2D(mapIndex, vUv);
    float indexedColor = mapColor.x;
    vec4 lookupColor = texture2D(lookup, vec2(indexedColor, 0.0));
    float outlineColor = texture2D(outline, vUv).x;
    vec4 depth = texture2D(depthTexture, vUv);
    vec4 earthColor = vec4(0.0);

    if (lookupColor.x == 1.0) {
      if (outlineColor > 0.2) {
        earthColor = vec4(lineColor, 0.6);
      } else {
        earthColor = vec4(mix(surfaceColor, vec3(indexedColor), 0.0), 1.0);
      }
    } else if (lookupColor.x == 0.0) {
      if(depth.x > 0.4) {
        earthColor = vec4(mix(surfaceColor, depth.xyz, 0.1), 1.0);
      } else {
        earthColor = vec4(mix(surfaceColor, depth.xyz, 0.3), 1.0);
      }
    }

    gl_FragColor = earthColor;

  }

顶点着色器

  varying vec3 vNormal;

  void main() {
    vNormal = normalize(normalMatrix * normal);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }

五、地球大气

地球是有一层大气层的,在屏幕上显示为一层朦朦的光圈。

实现思路:做一个半径比地球稍大一点的SphereGeometry,编写着色器使用ShaderMaterial做材质,将做成的Mesh加入到Scene中即可。这里列出着色器代码。

片元着色器:

  uniform float c;
  uniform float p;
  uniform vec3 color;
  varying vec3 vNormal;
  void main() {
    float intensity = pow(c - dot(vNormal, vec3(0.0, 0.0, 0.7)), p);
    gl_FragColor = vec4(color, 1.0) * intensity;
  }

顶点着色器:

  varying vec3 vNormal; 

  void main() {
    vNormal = normalize(normalMatrix * normal);
    gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
  }

六、效果

GIS与WebGL——可视化效果之绘制地球