原文链接及内容

当用户寻求帮助时,某些问题比其他问题更容易出现。本文档尝试列出在Stack Overflow上经常提问的一些问题。
如果你认为应该在此处添加一个问题(以及它的答案),请随时联系我们或在Github上提交一个pr(Pull Request)以增强本文档。

注:额外地,这里用一张图来解释一下上述Pull Request的意思:

OpenLayers使用的是什么投影?

使用OpenLayers创建的每个地图都将有一个地图视图(View),每个视图都将有一个投影,这是因为地球是三维的和圆形的,但地图是2D视图,因此我们需要一个数学表达式来呈现它,这便是投影
实际上不只是有一种投影,而是有许多常见的投影,每个投影都有不同的属性,因为它可以准确地表示距离、角度或面积。某些投影可能更适合世界的不同地区(区域)。
回到最初的问题:OpenLayers能够处理大多数投影。如果你没有明确设置投影,那么地图将使用Openlayers的默认设置,即Web墨卡托投影(EPSG:3857)。同样地,投影也用于OpenStreetMap项目的地图,以及商业化产品像必应地图或谷歌地图。
如果你想要一张显示整个世界的地图,上述Web墨卡托投影是一个很好的选择,如果你想使用OpenStreetMap或Bing瓦片地图,也同样需要使用这个投影。

如何更改地图的投影?

实际上,你很有可能希望将OpenLayers的默认投影更改为更适合你所在地区的投影,或着是特定数据的投影。
地图的投影可以通过View对象属性进行设置,以下是一些例子:

1
2
3
4
5
6
7
8
9
10
11
import Map from 'ol/Map.js';
import View from 'ol/View.js';

// OpenLayers支持WGS84坐标系(EPSG:4326)的支持
const map = new Map({
view: new View({
projection: 'EPSG:4326'
// ...
})
// ...
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import proj4 from 'proj4';
import {register} from 'ol/proj/proj4.js';
import {get as getProjection} from 'ol/proj.js';

// 要使用其他投影,则必须在OpenLayers中注册投影,
// 这可以通过[proj4](http://proj4js.org/)库轻松完成。
//
// 默认情况下,OpenLayers不知道`EPSG:21781`(Swiss)投影,
// 因此,我们需要为`EPSG:21781`创建了一个投影实例,并将其注册,
// 以使Openlayers库可以通过其代码(即EPSG代码)进行查找。
proj4.defs('EPSG:21781',
'+proj=somerc +lat_0=46.95240555555556 +lon_0=7.439583333333333 +k_0=1 ' +
'+x_0=600000 +y_0=200000 +ellps=bessel ' +
'+towgs84=660.077,13.551,369.344,2.484,1.783,2.939,5.66 +units=m +no_defs');
register(proj4);
const swissProjection = getProjection('EPSG:21781');

//注册后便可以使用上Swiss述投影
const map = new Map({
view: new View({
projection: swissProjection
// ...
})
// ...
});

我们建议在epsg.io上查找投影的参数(如有效性范围)。

为什么我的地图以几内亚湾(或非洲、海洋、空岛)为中心?

如果你在地图视图中设置了一个中心(center),但在视觉输出中没有看到真正的变化,那么很可能你在错误的投影中提供了地图中心的坐标(即坐标不匹配当前投影)。
由于OpenLayers中的默认投影是Web墨卡托(如上所述),因此必须提供在该投影下的中心的坐标,你的地图很可能如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import OSM from 'ol/source/OSM.js';

const washingtonLonLat = [-77.036667, 38.895];
const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: washingtonLonLat,
zoom: 12
})
});

这里提供了[-77.036667,38.895]作为视图的中心,但由于Web墨卡托是公制投影(以米为单位),因此这么设置会告诉OpenLayers,中心应该距离[0,0]几米(分别为-77m-39m)而在Web墨卡托投影中,坐标正好在几内亚湾。

解决方法很简单:提供投影到Web墨卡托的坐标。OpenLayers提供了一些有用的方法来帮助你:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import OSM from 'ol/source/OSM.js';
import {fromLonLat} from 'ol/proj.js';

const washingtonLonLat = [-77.036667, 38.895];
const washingtonWebMercator = fromLonLat(washingtonLonLat);

const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: washingtonWebMercator,
zoom: 8
})
});

fromLonLat()方法从Openlayers的3.5版开始可用。
如果你在Openlayers中注册一个自定义投影(见上面),你可以使用下面的方法将一个坐标从WGS84转换到你的投影:

1
2
3
import {transform} from 'ol/proj.js';
// 这里假设已经注册21871投影
const swissCoord = transform([8.23, 46.86], 'EPSG:4326', 'EPSG:21781');

为什么坐标顺序是[经度,纬度],而不是[纬度,经度]?

因为有两种不同且互不相容的约定。坐标通常是按纬度和经度这个顺序给出的。地图是地球表面的二维投影,坐标使用的笛卡尔坐标系的x,y网格表示,因为按照惯例,它们是在左边画西边,在上面画北边,这意味着x代表经度,y代表纬度。如上所述,OpenLayers旨在处理所有投影,但默认视图是在投影的笛卡尔坐标中。在笛卡尔x,y坐标系和经纬度系统中没有必要使用重复函数来处理坐标,因此输入纬度和经度时应将它们视为笛卡尔坐标,换句话说,书写顺序是lon,lat
如果你记不住是哪个方向,可以使用英语的语言代码en作为助记符:先东(east)后北(north)。

一个实际的例子

倘若你想要将你的地图放在地球上的某个地方,显然你需要有它的坐标。假设你希望地图以奥地利一个美丽的地方Schladming为中心,打开维基百科的这个页面:https://zh.wikipedia.org/wiki/%E6%96%BD%E6%8B%89%E5%BE%B7%E6%98%8E ,如下图在右上角有这个地方的坐标:
查看Schladming的坐标
上述坐标如下:
WGS84:
47° 23′ 39″ N, 13° 41′ 21″ E
47.394167, 13.689167

因此,下一步是将转换后的小数坐标放入一个数组中,并将其用作地图视图的中心坐标:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import TileLayer from 'ol/layer/Tile.js';
import OSM from 'ol/source/OSM.js';
import {fromLonLat} from 'ol/proj.js';

const schladming = [47.394167, 13.689167];
// 因为我们使用的是OSM(即Web墨卡托投影),所以我们必须转换坐标...
const schladmingWebMercator = fromLonLat(schladming);

const map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: 'map',
view: new View({
center: schladmingWebMercator,
zoom: 9
})
});

运行上面的例子可能会让你大吃一惊,因为我们的中心不是奥地利的施拉德明,而是也门的一个地区阿比扬(可能也是一个好地方)。那到底发生了什么?
许多人混淆了坐标数组中的经度和纬度的顺序,如果你一开始弄错了也不用担心,许多OpenLayers开发者在尝试改变地图中心的时候,不得不再三考虑是先放经度还是先放纬度。那么,让我们反转一下坐标:

1
2
const schladming = [13.689167, 47.394167];
//其余代码略

现在,Schladming现在正确地显示在地图的中央。
因此,当你在OpenLayers中处理EPSG:4326坐标时,请将经度放在前面,然后是纬度。这种行为与我们在OpenLayers 2中所看到的是一样的,因为按照WGS84坐标轴顺序还真说的通(上述的先东后北)。
如果你实在记不住正确的顺序,只需看看我们使用的方法名:fromLonLat,暗示我们需要先写经度,后写纬度。

为什么我的数据源中没有任何要素?

假设你要加载KML文件并在地图上显示包含的要素,可以使用如下代码:

1
2
3
4
5
6
7
8
9
import VectorLayer from 'ol/layer/Vector.js';
import KMLSource from 'ol/source/KML.js';

const vector = new VectorLayer({
source: new KMLSource({
projection: 'EPSG:3857',
url: 'data/kml/2012-02-10.kml'
})
});

你可能会问自己该KML文件中有多少个要素,并尝试如下所示:

1
2
3
4
5
6
7
8
9
10
11
import VectorLayer from 'ol/layer/Vector.js';
import KMLSource from 'ol/source/KML.js';

const vector = new VectorLayer({
source: new KMLSource({
projection: 'EPSG:3857',
url: 'data/kml/2012-02-10.kml'
})
});
const numFeatures = vector.getSource().getFeatures().length;
console.log("Count right after construction: " + numFeatures);

这将在控制台的日志中记录0个要素的计数。这是因为KML文件的加载将以异步方式进行。要尽快获得计数(就在提取文件并且在数据源对象中添加要素之后),你应该在数据源上使用事件侦听器函数:

1
2
3
4
5
6
7
vector.getSource().on('change', function(evt){
const source = evt.target;
if (source.getState() === 'ready') {
const numFeatures = source.getFeatures().length;
console.log("Count after change: " + numFeatures);
}
});

这时才能获取正确的要素数量,不出意外的话结果应为1119。

如何强制重新渲染地图?

通常,一旦数据源对象发生更改(例如,当远程数据源已加载时),就会自动重新渲染地图。
若要手动触发渲染,则可以使用:

1
map.render();

或者上述方法的伴随方法(companion method):

1
map.renderSync();

为什么找不到我加载的要素?

在使用Map的forEachFeatureAtPixelhasFeatureAtPixel方法时,会发现它们有时不适用于大图标或标签,命中检测仅检查在给定位置的特定距离内的要素。针对大图标,要素的实际几何形状可能由于太远而不会被考虑。
在这种情况下,设置VectorLayer的renderBuffer属性(默认值是100px):

1
2
3
4
5
6
import VectorLayer from 'ol/layer/Vector.js';

const vectorLayer = new VectorLayer({
...
renderBuffer: 200
});

推荐值为最大符号、线宽或标签的大小。

为什么在地图上进行缩放或单击时会被关闭(或译为不起作用)或变得不准确?

在调整地图容器元素的大小时,若OpenLayers没有更新地图,这可能是由渐进式更新CSS样式或手动调整地图大小引起的。当这种情况发生时,任何交互都将会变得不准确: 地图将放大和缩小,并最终不在指针(鼠标指针)上居中。这使得很难进行某些交互,例如选择所需的要素。
目前没有内置的方法来对元素的大小变化做出反应,因为Resize Observer API只在Chrome中实现。
然而有一种简单解决办法,那便是使用polyfill

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import Map from 'ol/Map.js';
import ResizeObserver from 'resize-observer-polyfill';

const mapElement = document.querySelector('#map');
const map = new Map({
target: mapElement
});

const sizeObserver = new ResizeObserver(() => {
map.updateSize()
});
sizeObserver.observe(mapElement);

// 当地图被销毁时调用的方法
// sizeObserver.disconnect()

评论
avatar
风停在左肩
生活原本沉闷,但跑起来就有风
公告
近期一直在复现、学习Cesium的官方示例,先快速过一遍示例,复杂的或看不懂的可以放之后再过一遍。