近期翻阅博客,看到社区大神一休哥的一篇
《canvas 奇巧淫技(二)绘制箭头路径效果》
文章,同样,该大神还展示过一个使用rbush库如何在前端快速从海量数据进行空间检索的案例:
https://alex2wong.github.io/mapbox-plugins/examples/rbush/
,很有分享精神的前端GIS专家,更多关于前端GIS检索数据的技术可参考搜狐的干货专访:
《深入理解空间搜索算法 ——数百万数据中的瞬时搜索》
。关于轨迹样式带导航箭头这种常见问题,笔者基于兴趣和朋友们的总结,也试着用熟悉的OpenLayers的StyleFunction去实现一个这样的玩具,在此分享给大家。
一 箭头数量
由高德轨迹箭头图可知,每隔固定像素,打上一个箭头。假设当前的线LineString地理长度为length,当前固定像素间隔stpes=n像素,在当前地图比例尺res已知的情况下,n像素地理距离是res
n,那么箭头总数count=length/(res
n):
let length=line_geom.getLength();
const steps=40;
let geo_steps=map_res*steps;
let arrow_count=length*1.0/geo_steps;
多么浅显易懂的道理啊,第一个问题很顺利的解决了。
二 箭头位置
第一步得到了箭头的总数,在获取箭头位置时,一个重要的API是线条LineString的getCoordinateAt,利用它我们在轨迹线上获取箭头点的位置。
fraction:参考点的百分比,如0就是LineString的起点,1就是LineString的终点,0.5就是LineString的中点。
linestring.getCoordinateAt(fraction, opt_dest)
假如箭头总数为arrowsNum,那么arrowsNum个箭头的数量分别是
for(let i=1;i<arrowsNum;i++){
let arraw_coor=geometry.getCoordinateAt(i*1.0/arrowsNum);
console.log(arraw_coor);
得到每个箭头的位置后,我们先可视化下吧,OpenLayers的地图样式完全由StyleFunction实现的,完整样式代码如下:
feature:地图上的要素对象,既有属性,也有坐标图形。
res:当前地图分辨率参数。
return:返回一个定制的渲染样式
var styleFunction = function(feature,res){
var trackLine= feature.getGeometry();
var styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#2E8B57',
width: 10
let length=trackLine.getLength();
let stpes=40;
let geo_steps=stpes*res;
let arrowsNum=parseInt(length/geo_steps);
for(let i=1;i<arrowsNum;i++){
let arraw_coor=trackLine.getCoordinateAt(i*1.0/arrowsNum);
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(arraw_coor),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: '#ffcc33'
}));
return styles;
一条完整的轨迹由多个连续的segment组成。
通过getCoordinateAt方法计算得到的箭头点,一定是在轨迹线上的某个点。
每个箭头点的方向是由箭头点落在的segment的方向决定的。
很显然,计算箭头方向其实就是计算每个箭头点到底落在了哪个segment上,将segme方向赋予箭头点。这里我们引入了rbush库构建空间索引,计算轨迹点与segment对应关系。
之所以我要引入rbush库,是解决循环计算问题,想象下如果不引入rbush库,只能使用如下的伪代码暴力计算了:
for(let i=0;i<arrows.length;i++){
for(let j=0;j<segments.length;j++){
if(instersects(arrows[i],segments[j])===true){
break;
感觉逻辑很简单啊,这样做难道不可以吗?想象下,箭头数量,segment的数量其实都是不可控的,一个复杂的轨迹线可能由成百上千的近万的segments,这样一个个循环去匹配,效率是不是就有问题了?所以引入了空间索引。这里查询,使用了rbush进行btree查询,查询的结果后再详细比对是否和箭头相交,累了,直接贴代码了,不详述了:
var styleFunction = function(feature,res){
var trackLine= feature.getGeometry();
var styles = [
new ol.style.Style({
stroke: new ol.style.Stroke({
color: '#2E8B57',
width: 10
let tree= rbush();
trackLine.forEachSegment(function(start, end) {
var dx = end[0] - start[0];
var dy = end[1] - start[1];
let rotation = Math.atan2(dy, dx);
let geom=new ol.geom.LineString([start,end]);
let extent=geom.getExtent();
var item = {
minX: extent[0],
minY: extent[1],
maxX: extent[2],
maxY: extent[3],
geom: geom,
rotation:rotation
tree.insert(item);
});
let length=trackLine.getLength();
let stpes=40;
let geo_steps=stpes*res;
let arrowsNum=parseInt(length/geo_steps);
for(let i=1;i<arrowsNum;i++){
let arraw_coor=trackLine.getCoordinateAt(i*1.0/arrowsNum);
let tol=10;
let arraw_coor_buffer=[arraw_coor[0]-tol,arraw_coor[1]-tol,arraw_coor[0]+tol,arraw_coor[1]+tol];
var treeSearch = tree.search({
minX: arraw_coor_buffer[0],
minY: arraw_coor_buffer[1],
maxX: arraw_coor_buffer[2],
maxY: arraw_coor_buffer[3]
});
let arrow_rotation;
if(treeSearch.length==1)
arrow_rotation=treeSearch[0].rotation;
else if(treeSearch.length>1){
let results=treeSearch.filter(function(item){
let _tol=1;
if(item.geom.intersectsExtent([arraw_coor[0]-_tol,arraw_coor[1]-_tol,arraw_coor[0]+_tol,arraw_coor[1]+_tol]))
return true;
if(results.length>0)
arrow_rotation=results[0].rotation;
styles.push(new ol.style.Style({
geometry: new ol.geom.Point(arraw_coor),
image: new ol.style.Icon({
src: '../static/content/images/arrowright.png',
anchor: [0.75, 0.5],
rotateWithView: true,
rotation: -arrow_rotation
}));
return styles;