到目前为止,当我们使用栅格数据(例如XYZ瓦片源)时,我们仅仅将其用于演示目的——即将数据直接渲染到地图上。然而对我们从数据中获取的像素值在渲染前进行处理也是可能的。Raster数据源提供一种对任意数量的输入的数据进行像素处理的方式。当数据源被使用在一个Image图层上市,栅格处理的结果可以被渲染到地图上。
在本次练习中,我们将要使用高程数据来作为XYZ瓦片。我们将在数据被渲染前对其进行逐像素的操作,而不是直接渲染编码过的高程数据。
Map setup 设置地图
编辑index.html文件使得我们可以渲染一个整页的图片:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>OpenLayers</title>
<style>
html, body, #map-container {
margin: 0;
height: 100%;
width: 100%;
font-family: sans-serif;
}
</style>
</head>
<body>
<div id="map-container"></div>
</body>
</html>
在一开始,地图将以圣地亚哥为中心,显示一个单独的XYZ的数据源。main.js代码如下:
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import XYZSource from 'ol/source/XYZ';
import {fromLonLat} from 'ol/proj';
new Map({
target: 'map-container',
layers: [
new TileLayer({
source: new XYZSource({
url: 'http://tile.stamen.com/terrain/{z}/{x}/{y}.jpg'
})
})
],
view: new View({
center: fromLonLat([-117.1493, 32.6965]),
zoom: 12
})
});
Render elevation data 渲染高程数据
我们将要使用在PNG瓦片中编码的高程数据(前往the Mapbox post查看更多细节)。在本次练习中,你需要注册sign up for a Mapbox account并使用对瓦片的访问令牌。
我们想要在高程数据被渲染前对其进行操作,但是在一开始,我们将要把Terrain-RGB的瓦片加入到地图中来看看他们长什么样。首先创建一个含有Terrain-RGB URL和访问令牌的XYZ的数据源。
const elevation = new XYZSource({
url: 'https://api.mapbox.com/v4/mapbox.terrain-rgb/{z}/{x}/{y}.pngraw?access_token=' + key,
crossOrigin: 'anonymous'
});
接着创建一个使用高程数据源的瓦片图层,并将这个图层加入到map中(这个图层写在下面,图层的array中的元素依次从最底层开始渲染):
new TileLayer({
opacity: 0.8,
source: elevation
})
现在地图的原始图层上就又蒙上了一层奇妙的颜色。Terrain-RGB瓦片的高程数据是以红绿蓝三个通道编码的,所以尽管这些数据不是纯粹的被直接渲染的,但是看上去很有意思。
Render sea level 渲染海平面
在之前的步骤中,我们直接在地图上渲染了Terrain-RGB的瓦片。然而我们想要做的是在地图上渲染海平面。并且我们想要我们的用户能够调整在海平面上的高度,查看跳帧高度后在地图上的渲染效果。我们将使用一个栅格的数据源去直接使用高程数据,并获取用户通过滑条输入的数据。
首先增加一些组件。在index.html中添加label和input标签:
<label id="slider">
Sea level
<input id="level" type="range" min="0" max="100" value="1"/>
+<span id="output"></span> m
</label>
现在为组件增加样式:
#slider {
position: absolute;
bottom: 1rem;
width: 100%;
text-align: center;
text-shadow: 0px 0px 4px rgba(255, 255, 255, 1);
}
与上次直接渲染Terrain-RGB图层的RGBA值不同,这次我们将要在渲染前操作像素值。栅格数据源允许我们接收任意数量的输入和一个操作来实现这个东西。这个操作是一个可以被所有输入源像素所调用的函数。我们只有一个输入源(高程),所以它将被一个数组的像素点调用,其中每一个像素点都是一个[red, green, blue, alpha]的数组。这个操作也会被data对象调用。我们将使用data对象去传递滑动条输入的值。
首先引入RasterSource和ImageLayer:
import ImageLayer from 'ol/layer/Image';
import RasterSource from 'ol/source/Raster';
在main.js中添加下面的方法。这个方法会对输入的高程数据进行解码:将RGB值转换成一个单独的高程测量值。对于等于或低于用户选择值的高程值,该函数将返回一个部分透明的蓝色像素,对于高于用户选择值的高程值,函数讲返回一个透明的像素:
function flood(pixels, data) {
const pixel = pixels[0];
if (pixel[3]) {
// decode R, G, B values as elevation
const height = -10000 + ((pixel[0] * 256 * 256 + pixel[1] * 256 + pixel[2]) * 0.1);
if (height <= data.level) {
// sea blue
pixel[0] = 145; // red
pixel[1] = 175; // green
pixel[2] = 186; // blue
pixel[3] = 255; // alpha
} else {
// transparent
pixel[3] = 0;
}
}
return pixel;
}
使用单个数据源(高程数据)创建一个栅格源,并使用flood函数对其进行配置:
const raster = new RasterSource({
sources: [elevation],
operation: flood
});
监听滑动条的输入值变化,并且在用户调整这个值的时候重新运行栅格操作:
const control = document.getElementById('level');
const output = document.getElementById('output');
control.addEventListener('input', function() {
output.innerText = control.value;
raster.changed();
});
output.innerText = control.value;
beforeoperations 事件会在栅格源的像素操作被执行前被启动。这是我们为这个操作提供额外数据的时机。在中国样例中,我们想要提供范围输入值(海拔米)。
raster.on('beforeoperations', function(event) {
event.data.level = control.value;
});
最后,通过将源添加到图像层来渲染光栅操作的输出。 将瓦片图层替换为使用我们的栅格源的图像图层(修改map的layers数组):
new ImageLayer({
opacity: 0.8,
source: raster
})
现在可以通过控制滑动条的输入值来改变海平面的高度了。