官网demo地址:
这篇讲的是如何通过Style同时给多边形和多边形的顶点设置不同的样式。
首先实例化一个地图,将地图中心点设置为北京
const map = new Map({
layers: [layer],
target: "map",
view: new View({
projection: "EPSG:4326",
center: [116.4, 39.9],
zoom: 8,
}),
});
然后新增了一个对象,里面是GeoJSON格式的数据
const geojsonObject = {
type: "FeatureCollection",
crs: {
type: "name",
properties: {
name: "EPSG:4326",
},
},
features: [
{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[116.2, 39.8],
[116.2, 40.0],
[116.6, 40.0],
[116.6, 39.8],
[116.2, 39.8],
],
],
},
},
{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[116.4, 39.7],
[116.4, 39.9],
[116.8, 39.9],
[116.8, 39.7],
[116.4, 39.7],
],
],
},
},
{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[116.0, 39.6],
[116.0, 39.8],
[116.4, 39.8],
[116.4, 39.6],
[116.0, 39.6],
],
],
},
},
{
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
[
[116.5, 39.5],
[116.7, 39.7],
[116.9, 39.5],
[116.5, 39.5],
],
],
},
},
],
};
重点是设置样式:
const styles = [
new Style({
stroke: new Stroke({
color: "blue",
width: 3,
}),
fill: new Fill({
color: "rgba(0, 0, 255, 0.1)",
}),
}),
new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({
color: "orange",
}),
}),
geometry: function (feature) {
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
}),
];
这段代码信息量可有点多。。。让我不得不思考以下问题:
1、 为什么可以用一套数据来完成两种不同的形状呢?
答:一般来说,如果我们想要在地图上画两种不一样的feature,第一反应肯定是准备两套不同的feature数据绘制两个图层,而这里为什么用一套数据来画了两种形状呢?其实是因为多个圆形和多边形的坐标点恰好相同,如果我们想要在多边形的顶点画六边形而不是圆形那现有数据大概可能也许是画不出来的(话不能说太满,除非有把点直接转成六边形的方法^_^)。
2、如果不写第二个Style会怎么样?
答:如果不写第二个Style,地图上的多边形也会画出来,只是没有顶点的圆形。
3、如果第一个Style中也写上geometry且不写返回值会怎样?
答:多边形也不会绘制出来
const styles = [
new Style({
stroke: new Stroke({
color: "blue",
width: 3,
}),
fill: new Fill({
color: "rgba(0, 0, 255, 0.1)",
}),
geometry: function (feature) {
console.log('第一个style中的geometry执行了');
},
}),
new Style({
image: new CircleStyle({
radius: 5,
fill: new Fill({
color: "orange",
}),
}),
geometry: function (feature) {
console.log("第二个style中的geometry函数执行了");
// return new MultiPoint(coordinates);
},
}),
4、为什么要在geometry函数里return new MultiPoint(coordinates)?如果不写return会怎样?
答:geometry函数相当于把绘制流程做了一个截断,如果不写return不会画出来顶点,而在这里new MultiPoint实际上是取了一个巧,前面我们已经说过多个点和多边形用的坐标点是一样的,这里直接把坐标点取出来创建了一个MultiPoint类型的geometry并return了出去。从而画出了顶点。
总结:这里之所以能画出来两种图形主要是在于写了两个样式,并在其中一个样式中使用geometry截断了openlayers的绘制流程,取出坐标点位使用new MultiPoint再画了几个点。从而在多边形的顶点上画出了圆形。
这个示例中用到了geometry,不禁让我想起了renderer,于是想着来扒一扒他两的异同。。。
(以下为个人扩展部分,没有去看openlayers的源码,纯属个人理解)
1、geometry和renderer执行顺序是怎样的?
我们知道,这两同时都有截断作用,那如果同时写会怎样?
geometry: function (feature) {
console.log("geometry函数执行了");
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
renderer: function (coordinates, state) {
console.log("renderer函数执行了");
},
结果是都执行了,且执行顺序是geometry函数先执行,renderer函数后执行。但是顶点并没有绘制出来。这是因为renderer截断了,renderer 里的参数是坐标点和canvas的上下文,如果想要圆形绘制出来需要在renderer里获取到state.context,继续写canvas的绘制代码才会绘制出来图形,类似绘制渐变圆形示例中的代码:
renderer(coordinates, state) {
console.log('state',state);
// 解构坐标数组,获取圆心和圆周上的一个点的坐标
const [[x, y], [x1, y1]] = coordinates;
const ctx = state.context;
const dx = x1 - x;
const dy = y1 - y;
//通过圆心和圆周点的坐标计算圆的半径。
const radius = Math.sqrt(dx * dx + dy * dy);
// 定义内外半径
//渐变的内半径为0,外半径为圆的1.4倍
const innerRadius = 0;
const outerRadius = radius * 1.4;
// 创建放射状渐变
const gradient = ctx.createRadialGradient(
x,
y,
innerRadius,
x,
y,
outerRadius
);
// 定义渐变的颜色停止点
gradient.addColorStop(0, "rgba(255,0,0,0)");
gradient.addColorStop(0.6, "rgba(255,0,0,0.2)");
gradient.addColorStop(1, "rgba(255,0,0,0.8)");
// 绘制圆并填充渐变色
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * Math.PI, true); //绘制一个圆形路径。
ctx.fillStyle = gradient; //设置填充样式为之前定义的渐变。
ctx.fill(); // 填充圆形区域
// 绘制圆的边界
ctx.arc(x, y, radius, 0, 2 * Math.PI, true);
ctx.strokeStyle = "rgba(255,0,0,1)";
ctx.stroke();
},
那如果geometry不写return,renderer还会执行吗?
我们把geometry中的return注释掉
geometry: function (feature) {
console.log("geometry函数执行了");
const coordinates = feature.getGeometry().getCoordinates()[0];
//return new MultiPoint(coordinates);
},
renderer: function (coordinates, state) {
console.log("renderer函数执行了")
},
答案是不会,geometry参数要么不写,要写的话就一定要有返回值。这也证明了执行顺序是先geometry后renderer。
2、geometry和renderer的参数有何不同?
我们可以来打印一下这两个函数的所有参数
geometry: function (feature) {
console.log("geometry的所有参数",arguments);
const coordinates = feature.getGeometry().getCoordinates()[0];
return new MultiPoint(coordinates);
},
renderer: function (coordinates, state) {
console.log("renderer的所有参数",arguments);
},
geometry的参数只有一个,返回了当前的feature,而renderer的参数是两个,一个是坐标点,第二个是上下文对象。
3、为什么renderer中的坐标点和geometry中的坐标点不一样?
在geometry中打印feature,会看到坐标点,这时跟我们定义feature时的坐标点是一样的。
而在renderer中获取到的坐标是这样的
为什么不一样?
其实是因为,地图上的坐标点位是地理坐标点位,而最终绘制成地图是使用的canvas,canvas的坐标是屏幕坐标,所以绘制地图时要把地理坐标点位转换为屏幕坐标。renderer中返回坐标就已经是屏幕坐标了。这也是为什么我们在renderer中获取state,写绘制canvas的代码,地图就可以绘制出来的原因。
来证实一下rederer中的点位是屏幕坐标。
我在地图上方写了一个div,让它的定位等于renderer中的某一个点位,并且写了一个按钮控制它的显隐,如果能重合,则证明它的点位是屏幕坐标。