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();
思路:
- 添加 EPSG:3857 和 EPSG:4326;
- 添加 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、流程
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;
}
);
}
三、总结
-
添加坐标系,主要有两步
- 添加坐标系
- 添加该坐标系与其他坐标系的转换函数
-
采用 new Projection() 时需配齐必要参数,且缺少与其他坐标系的转换函数
-
采用 register(proj4),解析 proj-string 定义时,会有参数不全的情况。(在 v6.5.0 之前的版本,添加 CGCS2000 时,会缺少 units 参数,若视图的坐标系定义为 CGCS2000,视图可能会构建失败。)
-
OpenLayers 默认采用 proj4 的 transform 转换函数
-
OpenLayers 的坐标系和转换函数是单例模式,projections.js 的 cache 和 transforms.js 的 transforms