让我们做一个特色编辑器吧!
在这个模块中,我们会创建一个基础的编辑器来处理矢量数据,我们的目标是让用户可以导入数据、绘制新的特征、修改现有特征和导出结果。在这个模块中我们使用的是GeoJSON数据,不过,如果你对其它资源数据感兴趣的话,OpenLayers也是可以支持很多格式的矢量数据的。
渲染GeoJSON
在编辑之前,我们先来看一下渲染矢量数据源和图层时的基本特征。工作台包括一个位于data目录中名为“countries.json”的GeoJSON文件,我们先从加载这个数据并且渲染至一个地图中开始。
首先,编辑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;
background-color: #04041b;
}
</style>
</head>
<body>
<div id="map-container"></div>
</body>
</html>
现在我们要引入三个重要内容来处理矢量数据:
- 一个读写序列化数据的格式文件(本例中是GeoJSON );
- 用于获取数据和管理特征的空间索引的矢量源(vector source);
- 用于在地图上渲染特征的矢量层(vector layer)。
更新main.js文件,使它能够加载和显示包含GeoJSON特征的文件:
import 'ol/ol.css';
import GeoJSON from 'ol/format/GeoJSON';
import Map from 'ol/Map';
import VectorLayer from 'ol/layer/Vector';
import VectorSource from 'ol/source/Vector';
import View from 'ol/View';
new Map({
target: 'map-container',
layers: [
new VectorLayer({
source: new VectorSource({
format: new GeoJSON(),
url: './data/countries.json'
})
})
],
view: new View({
center: [0, 0],
zoom: 2
})
});
现在你可以在自己创建的本地网页上看到一个带有国家边界的地图了,如下图所示:
GeoJSON特征
由于我们要频繁刷新页面,如果地图在重新加载时能一直留在原位置就再好不过了,这个功能可以通过引入ol-hashed包来实现。这个包早在安装工作台支持的时候就已经包含在里面了,如果没有的话。你可以通过代码“npm install ol-hashed”来补充安装、
然后使用下列代码引入main.js中:
import sync from 'ol-hashed';
现在我们要将地图赋值给一个变量(下面的map),使它能够传给sync函数:
const map = new Map({
现在我们可以给地图调用sync函数:
sync(map)
现在当网页再刷新时,你就能够看到地图一直在那里,并且返回按钮也能如你所料的那样起作用。
拖放
对于我们的特征编辑器来说,我们希望用户能够导入自己的数据进行编辑,此时可以使用DragAndDrop 交互。之前我们用GeoJSON 格式来解析特征,但这个交互通过配置使其处理各种数量的特征格式。
在这节里,我们会向许多组件中传递地图数据,所以一定要确保你将这部分数据赋值给了map变量:
const map = new Map({
在main.js中引入拖放交互方法:
import DragAndDrop from 'ol/interaction/DragAndDrop';
接下来,创建一个没有初始数据的矢量数据源,不像以前的例子那样加载从远程获取的云端数据,这个源将会存放用户拖动和投放在地图上的特征。
const source = new VectorSource();
现在从地图中移除旧的layer,创建一个矢量数据源为空的layer,添加至地图中:
const layer = new VectorLayer({
source: source
});
map.addLayer(layer);
最后,我们创建一个拖放交互,通过配置使它能处理矢量数据源,然后添加到地图中:
map.addInteraction(new DragAndDrop({
source: source,
formatConstructors: [GeoJSON]
}));
现在你拖动GeoJSON文件到地图中,就能看到他们被渲染的结果。
拖动文件到地图
修改特征
既然我们已经有让用户向编辑器加载数据的方法了,还想更进一步,让他们能够编辑特征。因此我们可以用 modify 交互来实现,通过配置在矢量源上修改特征。
首先,在main.js中引入 modify 方法:
import Modify from 'ol/interaction/Modify';
然后,创建一个新的交互,它的source就是我们正在使用的矢量数据源,然后把它添加到地图中(在main.js的底部):
map.addInteraction(new Modify({
source: source
}));
给地图加上数据后,验证一下是否能通过拖动地图上的点来修改特征,如果不想要了,按住“Alt”再单击它来删除。
修改特征
绘制新的特征
我们的特征编辑器现在已经可以用来加载数据并且修改特征了,接下来,我们要添加一个Draw交互方法,能够让用户添加新的特征并且把它们放入source中。
首先,在main.js中引入Draw方法:
import Draw from 'ol/interaction/Draw';
现在,创建一个交互,通过配置,让它能够画出多边形并添加到 source 中去:
map.addInteraction(new Draw({
type: 'Polygon',
source: source
}));
“type”属性决定了添加的是哪种几何图形,它的值可以是任何GeoJSON有的几何类型。注意我们也可以引入 GeometryType 的值(需先通过'ol/geom/GeometryType'导入方法),引入成功之后,要用 GeometryType.POLYGON 代替上面的 'Polygon' 字符串才能生效。
当我们在如图位置绘制特征,就能将这个特征添加到矢量源中。
加勒比地区的一个新岛国
对齐
你可能发现了,绘制的特征很容易与现有的功能不能很好地配合,此外,当修改特征时,会打破布局——在之前相邻的多边形之间增加一个空隙。snap交互的作用是在绘制或编辑特征的时候能够保持布局。
首先,在 js 文件中引入 snap 方法:
import Snap from 'ol/interaction/Snap';
像其它对特征进行操作的交互一样,我们要给snap配置source来处理矢量源,然后把它添加到地图中:
map.addInteraction(new Snap({
source: source
}));
当绘制、修改和对齐交互都可用时,我们编辑数据的时候就能保持原有的布局。
使用snap联结各个国家
下载特征
当上传了数据并且编辑完成后,我们想让用户可以下载最终的结果。要想实现这个功能,我们要将特征数据序列化为GeoJSON,并创建一个带有download属性的<a>标签,从而触发浏览器的文件保存对话框。同时,要在地图上添加一个用于清除现有特征并可以重新开始的按钮。
首先,使用一些HTML标签来代表按钮,在index.html文件的地图容器(之前的例子中 id 是 map-container):
<div id="tools">
<a id="clear">Clear</a>
<a id="download" download="features.json">Download</a>
</div>
为按钮添加样式,例如:
#tools {
position: absolute;
top: 1rem;
right: 1rem;
}
#tools a {
display: inline-block;
padding: 0.5rem;
background: white;
cursor: pointer;
}
先完成最容易的清除功能,矢量源有一个source.clear()方法,我们想在点击清除按钮时运行这个方法,所以在 js 文件中添加点击事件:
const clear = document.getElementById('clear');
clear.addEventListener('click', function() {
source.clear();
});
序列化想要下载的特征数据,我们需要使用 GeoJSON 格式。既然我们想在编辑的时候随时都能下载数据,就应该在source的每个change事件进行特征序列化,并且为download标签的herf属性构建一个URI数据:
const format = new GeoJSON({featureProjection: 'EPSG:3857'});
const download = document.getElementById('download');
source.on('change', function() {
const features = source.getFeatures();
const json = format.writeFeatures(features);
download.href = 'data:text/json;charset=utf-8,' + json;
});
清除和下载数据的按钮
美化
到了这一步,我们已经完成了带有引入、编辑和到处功能的特征编辑器,但是我们并没有花时间让特征看起来更好看。在OpenLayers创建一个向量layer的时候,会默认使用自带样式,编辑用的交互(绘制和修改)同样如此。你可能已经注意到了,在编辑过程中,几何图形的笔画比较粗。这些表现可以用向矢量层和编辑用交互添加style进行调整。
首先,引入需要的构造函数:
import {Style, Fill, Stroke} from 'ol/style';
静态样式
如果我们想给所有的特征都设定相同的样式,可以这样配置矢量层:
const layer = new VectorLayer({
source: source,
style: new Style({
fill: new Fill({
color: 'red'
}),
stroke: new Stroke({
color: 'white'
})
})
});
也可以给style属性设置一个数组的样式,这样就可以渲染出一个套色线(例如:下面是宽的笔画,上面是窄的笔画)。
虽然这里其实并没有一个很好的理由,但为了这次练习,我们将利用动态造型的优势。
动态样式
当你觉得应该根据特征的属性或当前视图的分辨率来为每一个特征分别渲染的话,你可以尝试用样式函数配置矢量层。这个函数会在每个渲染框架下的每个特征都调用一次,因此,如果你有很多特征且想保持好的渲染性能,这个函数一定要非常高效。
下面这个例子(完全虚构的例子),是根据name属性的首字母是“A-M”还是“N-Z”来选择不同的渲染特征的样式:
const layer = new VectorLayer({
source: source,
style: function(feature, resolution) {
const name = feature.get('name').toUpperCase();
return name < "N" ? style1 : style2; // assuming these are created elsewhere
}
});
基于几何形状的样式
为了了解动态样式是怎么使用的,我们可以构造一个基于几何形状渲染特征的样式函数。在此之前,需要用npm安装 colormap包,我们可以使用以下代码安装支持:
npm install colormap
现在,我们要引入 colormap 包和 ol/sphere 用于球形区域计算:
import {getArea} from 'ol/sphere';
import colormap from 'colormap';
接下来我们要写几个函数来对不同的几何区域设置不同的颜色:
const min = 1e8; // the smallest area
const max = 2e13; // the biggest area
const steps = 50;
const ramp = colormap({
colormap: 'blackbody',
nshades: steps
});
function clamp(value, low, high) {
return Math.max(low, Math.min(value, high));
}
function getColor(feature) {
const area = getArea(feature.getGeometry());
const f = Math.pow(clamp((area - min) / (max - min), 0, 1), 1 / 2);
const index = Math.round(f * (steps - 1));
return ramp[index];
}
现在我们可以给矢量层的style属性增加一个函数,用它可以实现基于几何区域填充不同的颜色的功能:
const layer = new VectorLayer({
source: source,
style: function(feature) {
return new Style({
fill: new Fill({
color: getColor(feature)
}),
stroke: new Stroke({
color: 'rgba(255,255,255,0.8)'
})
});
}
});
按区域着色的特征
网友评论