最近碰到这么一个需求:有一些面(Polygon)和线(LineString),需要在面的边线上或者在线上均匀绘制一些矩形图标(在各种分辨率下)。在一个Polygon的边线或者LineString上绘制一个矩形图标时,图标会遮住线,如果图标是部分镂空的,就会看到线从图标中穿过,如何让图标看起来遮住了线呢,自然而然就会想到:只需要将图标和线做相交操作,取图标外的两条线段就行了。那么需要依次解决如下问题:
- 如何将矩形图标区域和线做相交计算,并算出来不在矩形区域内的两条线段
- 在openlayer中,缩放地图时如何动态去绘制出计算出来的线段和矩形图标
首先,第一个问题很好解决,我们只需要使用turf.js的API即可,使用 turf.lineIntersect(line, polygon)计算出线和线上的矩形图标的相交点,然后使用相交点和线段的起点或者终点进行连接即可。由于我的需求是点图标必须在线的中心线,先将线以中心线切分成两条线段,然后使用中心点的坐标和矩形图标的宽高计算出图标矩形的polygon,然后分别用切分的两条线段和图标polygon进行相交计算,计算出来的交点必为新线段的一个点,然后使用 turf.booleanPointInPolygon(line[0], polygon)计算线段的第一个点在不在矩形内,如果在,说明我们应该线的取第二个点来和交点组成新的线段
const getSplitLineString = function (lineString, iconPoints, resolution) {
if (!iconPoints || iconPoints.length === 0) {
return lineString
}
// 获取icon的矩形区域
let iconPoint = iconPoints.shift()
let p1 = this.getIconPolygon(iconPoint, resolution)
// 计算线段与矩形的交点
let intersect = turf.lineIntersect(turf.lineString(lineString), p1)
let intersectCoord = turf.coordAll(intersect)[0]
// 如果线段的第一个点在icon内,就取第二个点和交点连接成线
let firstIn = turf.booleanPointInPolygon(turf.point(lineString[0]), p1)
let newLine = [firstIn ? lineString[1] : lineString[0], intersectCoord]
return getSplitLineString(newLine, iconPoints, resolution)
}
接下来就是重头戏了,在openlayer中,缩放地图时如何动态去绘制出计算出来的线段和矩形图标。
需要解决这个问题,那就需要深入了解openlayer的map的渲染的逻辑。这里我们使用的是ol.layer.Vector。那就深入研究下openlayer地图是如何渲染feature的(注:我使用的openlayer版本为4.6.5,现在新版本api基本上大同小异)
openlayer地图渲染流程分析图
上面我简单梳理了下openlayer是如何渲染的,大致是这样,在ol.Map中全局注册了一些地图的Renderer和图层的Renderer,这个类比较重要,以layer的renderer来举例,通过继承类ol.renderer.Layer或其子类,编写自己的LayerRender,编写相关的钩子方法,就可以自定义绘制图层:
- 在类上挂载一个静态钩子方法handles(type, layer),这样地图就能知道你的LayerRender处理的是哪种类型的Layer
- 挂载一个静态钩子方法create(mapRenderer, layer) 用于初始化此render
- 实现钩子方法prepareFrame(frameState, layerState)、composeFrame(frameState, layerState, context)来实现图层的重绘(地图发生变化时,均会重绘地图,从而执行钩子方法)
其他流程分析可以参考上面的分析图。在prepareFrame有一段非常重要的代码,是本次需要的
var vectorLayer = /** @type {ol.layer.Vector} */ (this.getLayer());
var vectorSource = vectorLayer.getSource();
// ....中间代码省略
if (vectorLayerRenderOrder) {
/** @type {Array.<ol.Feature>} */
var features = [];
vectorSource.forEachFeatureInExtent(extent,
/**
* @param {ol.Feature} feature Feature.
*/
function(feature) {
features.push(feature);
}, this);
features.sort(vectorLayerRenderOrder);
for (var i = 0, ii = features.length; i < ii; ++i) {
renderFeature(features[i]);
}
} else {
vectorSource.forEachFeatureInExtent(extent, renderFeature, this);
}
可以看到,在这个钩子方法里调用的layer.getSouce()方法来获取图层source,并使用了vectorSource.forEachFeatureInExtent方法来处理每一个feature,那么,正好就有时机介入来处理我们的feature了
ol.source.Vector.prototype.forEachFeatureInExtent = function(extent, callback, opt_this) {
if (this.featuresRtree_) {
return this.featuresRtree_.forEachInExtent(extent, callback, opt_this);
} else if (this.featuresCollection_) {
return this.featuresCollection_.forEach(callback, opt_this);
}
};
forEachFeatureInExtent 方法并不复杂,只是遍历了内部数据源的feature并执行了传入的回调。所以我们只需要编写一个新的类继承ol.source.Vector类,并重写forEachFeatureInExtent方法即可,伪代码如下
var MyVectorSource = function (opt_options) {
ol.source.Vector.call(this, opt_options)
}
ol.inherits(MyVectorSource, ol.source.Vector);
MyVectorSource.prototype.forEachFeatureInExtent = function (extent, callback, opt_this) {
let features = [];
ol.source.Vector.prototype.forEachFeatureInExtent.call(
this,
extent,
function (feature) {
// 这里将处理ol.souce.Vector里原始的feature
// 我们现在可以切分线了,并将切分完成的feature放入到数组中,具体代码省略
features.push(handleYourFeature(feature));
},
opt_this
);
for (let i = 0, ii = features.length; i < ii; ++i) {
callback(features[i]);
}
};
标签:ol,features,openlayer,线段,feature,矩形,图标
From: https://blog.csdn.net/qq_41211797/article/details/140290396