Openlayers源码阅读(九):扩展坐标系

OpenLayers 在 v5 版本对 proj4 的集成做了修改。故本文采用最新的的版本 v6.5.0
OpenLayers 默认只加载了 EPSG:4326(WGS84 geographic coordinates) 和 EPSG:3857(Web or Spherical Mercator)

热身:加载 CGCS2000 地理坐标系

  • 方式 1:采用 new ol.proj.Projection()
import {
    
     Projection, addProjection } from "ol/proj";

let projection_4490 = new Projection({
    
    
  code: "EPSG:4490", // require
  units: "degree", // require
  extent: [73.62, 16.7, 134.77, 53.56],
  axisOrientation: "neu",
  metersPerUnit: (Math.PI * 6378137) / 180, // require
  global: false,
  worldExtent: [-180, -90, 180, 90],
});
addProjection(projection_4490);
  • 方式 2:采用 proj4.js
// 需安装proj4
// npm install proj4

import proj4 from "proj4";
import {
    
     register } from "ol/proj/proj4";

// 来源http://epsg.io/4490
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

方式一采用 OpenLayer 自身的对象和方法添加,units 和 metersPerUnit 会影响到视图和图层的渲染,务必配置。具体后续再做说明。
方式二:采用proj4的方式定义,想了解proj-string的可以查看另一篇文章解读坐标系定义之:proj4

一、默认加载

https://github.com/openlayers/openlayers/blob/v6.5.0/src/ol/proj.js

/**
 * Add transforms to and from EPSG:4326 and EPSG:3857.  This function is called
 * by when this module is executed and should only need to be called again after
 * `clearAllProjections()` is called (e.g. in tests).
 */
export function addCommon() {
    
    
  // Add transformations that don't alter coordinates to convert within set of
  // projections with equal meaning.
  addEquivalentProjections(EPSG3857_PROJECTIONS);
  addEquivalentProjections(EPSG4326_PROJECTIONS);
  // Add transformations to convert EPSG:4326 like coordinates to EPSG:3857 like
  // coordinates and back.
  addEquivalentTransforms(
    EPSG4326_PROJECTIONS,
    EPSG3857_PROJECTIONS,
    fromEPSG4326,
    toEPSG4326
  );
}

addCommon();

思路:

  1. 添加 EPSG:3857 和 EPSG:4326;
  2. 添加 EPSG:3857 和 EPSG:4326 之间的坐标转化函数.

二、proj4 加载

1、register

https://github.com/openlayers/openlayers/blob/main/src/ol/proj/proj4.js

export function register(proj4) {
    
    
  // 步骤1: 添加坐标系
  // 1. 获取proj4扩展的坐标系集合
  const projCodes = Object.keys(proj4.defs);
  const len = projCodes.length;
  let i, j;
  for (i = 0; i < len; ++i) {
    
    
    const code = projCodes[i];
    // 2. 判断坐标系是否已添加
    if (!get(code)) {
    
    
      // 3. 解析proj4的 proj-string定义的信息
      const def = proj4.defs(code);
      let units = def.units;
      // 4. 如果+proj为经纬度,则赋值单位
      if (!units && def.projName === "longlat") {
    
    
        units = Units.DEGREES;
      }
      // 5. 初始化坐标系,并添加
      addProjection(
        new Projection({
    
    
          code: code,
          axisOrientation: def.axis,
          metersPerUnit: def.to_meter,
          units,
        })
      );
    }
  }
  // 步骤二:添加坐标系之间的转换
  for (i = 0; i < len; ++i) {
    
    
    const code1 = projCodes[i];
    const proj1 = get(code1);
    for (j = 0; j < len; ++j) {
    
    
      const code2 = projCodes[j];
      const proj2 = get(code2);
      // 6. 判断两坐标系的转换函数是否存在
      if (!getTransform(code1, code2)) {
    
    
        const def1 = proj4.defs(code1);
        const def2 = proj4.defs(code2);
        // 7. 两坐标是否相等,
        if (def1 === def2) {
    
    
          addEquivalentProjections([proj1, proj2]);
        } else {
    
    
          // 8. 获取proj4的转化函数
          // Reset axis because OpenLayers always uses x, y axis order
          const transform = proj4(
            assign({
    
    }, def1, {
    
     axis: undefined }),
            assign({
    
    }, def2, {
    
     axis: undefined })
          );
          // 9. 添加两坐标系之间的转换函数
          addCoordinateTransforms(
            proj1,
            proj2,
            createSafeCoordinateTransform(proj1, proj2, transform.forward),
            createSafeCoordinateTransform(proj2, proj1, transform.inverse)
          );
        }
      }
    }
  }
}

2、流程

addCoordinateTransforms
transform
createTransformFromCoordinateTransform
addTransformFunc
createSafeCoordinateTransform

3、addCoordinateTransforms

export function addCoordinateTransforms(source, destination, forward, inverse) {
    
    
  const sourceProj = get(source);
  const destProj = get(destination);
  addTransformFunc(
    sourceProj,
    destProj,
    createTransformFromCoordinateTransform(forward)
  );
  addTransformFunc(
    destProj,
    sourceProj,
    createTransformFromCoordinateTransform(inverse)
  );
}

4、createSafeCoordinateTransform

export function createSafeCoordinateTransform(sourceProj, destProj, transform) {
    
    
  return function (coord) {
    
    
    let sourceX = coord[0];
    let sourceY = coord[1];
    let transformed, worldsAway;
    if (sourceProj.canWrapX()) {
    
    
      const sourceExtent = sourceProj.getExtent();
      const sourceExtentWidth = getWidth(sourceExtent);
      worldsAway = getWorldsAway(coord, sourceProj, sourceExtentWidth);
      if (worldsAway) {
    
    
        // Move x to the real world
        sourceX = sourceX - worldsAway * sourceExtentWidth;
      }
      sourceX = clamp(sourceX, sourceExtent[0], sourceExtent[2]);
      sourceY = clamp(sourceY, sourceExtent[1], sourceExtent[3]);
      transformed = transform([sourceX, sourceY]);
    } else {
    
    
      transformed = transform(coord);
    }
    if (worldsAway && destProj.canWrapX()) {
    
    
      // Move transformed coordinate back to the offset world
      transformed[0] += worldsAway * getWidth(destProj.getExtent());
    }
    return transformed;
  };
}

5、createTransformFromCoordinateTransform transform

export function createTransformFromCoordinateTransform(coordTransform) {
    
    
  return (
    /**
     * @param {Array<number>} input Input.
     * @param {Array<number>=} opt_output Output.
     * @param {number=} opt_dimension Dimension.
     * @return {Array<number>} Output.
     */
    function (input, opt_output, opt_dimension) {
    
    
      const length = input.length;
      const dimension = opt_dimension !== undefined ? opt_dimension : 2;
      const output = opt_output !== undefined ? opt_output : new Array(length);
      for (let i = 0; i < length; i += dimension) {
    
    
        const point = coordTransform([input[i], input[i + 1]]);
        output[i] = point[0];
        output[i + 1] = point[1];
        for (let j = dimension - 1; j >= 2; --j) {
    
    
          output[i + j] = input[i + j];
        }
      }
      return output;
    }
  );
}

三、总结

  1. 添加坐标系,主要有两步

    1. 添加坐标系
    2. 添加该坐标系与其他坐标系的转换函数
  2. 采用 new Projection() 时需配齐必要参数,且缺少与其他坐标系的转换函数

  3. 采用 register(proj4),解析 proj-string 定义时,会有参数不全的情况。(在 v6.5.0 之前的版本,添加 CGCS2000 时,会缺少 units 参数,若视图的坐标系定义为 CGCS2000,视图可能会构建失败。)

  4. OpenLayers 默认采用 proj4 的 transform 转换函数

    扫描二维码关注公众号,回复: 12890822 查看本文章
  5. OpenLayers 的坐标系和转换函数是单例模式,projections.js 的 cache 和 transforms.js 的 transforms

猜你喜欢

转载自blog.csdn.net/u013240519/article/details/113185819