需求
需要在地图上显示物联设备的状态,需要比较显眼的方式,通过一定的动态效果展示。通过矢量图层的样式其实是比较难实现一些好一点的动态效果的,查询API发现了有一个ImageCanvas的Source,通过canvas那就可以做很多事了。
实现方案
继承openlayers 的ImageCanvas,根据添加的Point和属性信息操作canvas画出效果,通过 window.requestAnimationFrame 实现动画效果。
openlayers6 中也可以通过layer的 prerender和postrender去更改样式中的参数实现动态效果,如修改样式的颜色,透明度,圆半径等
代码实现
//定义一个新的source
ol.source.AnimaSource = function (opt_options) {
let options = opt_options || {};
let map = this.map = options.map;
this.zIndex = options.zIndex;
this.offset = [0,0];
this.features = new Array();
this.idCahce = {}
this.isAnimation = false;
this.init = false;
let _this = this;
let canvas = this.canvas = document.createElement("canvas");
let context = this.context = this.canvas.getContext('2d');
ol.source.ImageCanvas.call(this,{
canvasFunction:_this.canvasFunc
});
this.map.on('change:size', this.resize.bind(this));
let scale = function (pixelP, center, scaleRatio) {
var x = (pixelP[0] - center[0]) * scaleRatio + center[0];
var y = (pixelP[1] - center[1]) * scaleRatio + center[1];
return [x, y];
}
//坐标转屏幕坐标
let transformCoordinate =function(coordinate) {
let x = (coordinate[0] - _this._mapCenter[0]) / _this._reselutions, y = (_this._mapCenter[1] - coordinate[1]) / _this._reselutions;
var scaledP = [x + _this._mapCenterPx[0], y + _this._mapCenterPx[1]];
scaledP = scale(scaledP, _this._mapCenterPx, 1);
return [scaledP[0] + _this.offset[0], scaledP[1] + _this.offset[1]];
};
// 动画render方法
let render = this.renderFunc = function () {
if(_this.aid!=null){
window.cancelAnimationFrame(_this.aid);
}
context.save();
context.clearRect(0,0,_this.canvas.width,_this.canvas.height);
for (let i=0;i<_this.features.length;i++){
let f= _this.features[i];
//调用实体的draw方法
f.draw(context,transformCoordinate);
}
context.restore();
map.render();
if(_this.isAnimation){
_this.aid = window.requestAnimationFrame(render);
}
}
};
// 指定source的画布方法
ol.source.AnimaSource.prototype.canvasFunc= function (extent, resolution, pixelRatio, size, projection) {
if(!this.init){
this.init = true;
this.startAnimation(this.renderFunc);
}
var mapWidth = size[0] / pixelRatio;
var mapHeight = size[1] / pixelRatio;
var width = this.map.getSize()[0];
var height = this.map.getSize()[1];
this.pixelRatio = pixelRatio;
let offset = [(mapWidth - width) / 2, (mapHeight - height) / 2];
this.offset[0] = offset[0];
this.offset[1] = offset[1];
this.canvas.style.cssText = "position:absolute;" + "left:"+offset[0]+";" + "top:"+offset[1]+";" + "z-index:" + this.zIndex + ";";
this.canvas.className = "myAnimClass";
this.canvas.width = size[0];
this.canvas.height = size[1];
this._mapCenter = this.map.getView().getCenter();
this._mapCenterPx = this.map.getPixelFromCoordinate(this._mapCenter);
this._reselutions = this.map.getView().getResolution();
this._rotation = this.map.getView().getRotation();
this.context.scale(pixelRatio,pixelRatio);
return this.canvas;
}
ol.source.AnimaSource.prototype.startAnimation = function () {
if(this.isAnimation==false){
this.isAnimation = true;
this.aid = window.requestAnimationFrame(this.renderFunc);
}
}
ol.source.AnimaSource.prototype.stopAnimation = function () {
if(this.aid!=null){
window.cancelAnimationFrame(this.aid);
this.aid = null;
}
this.isAnimation = false;
}
// 地图大小变化时,重新计算画布大小,坐标偏移
ol.source.AnimaSource.prototype.resize = function (evt) {
let mapSize = evt.target.getSize();
let mapWidth = mapSize[0];
let mapHeight = mapSize[1];
this._mapCenter = this.map.getView().getCenter();
this._mapCenterPx = this.map.getPixelFromCoordinate(this._mapCenter);
let global$2 = typeof window === 'undefined' ? {} : window;
let devicePixelRatio = this.devicePixelRatio = global$2.devicePixelRatio;
let offset = [(this.canvas.width/devicePixelRatio - mapWidth) / 2, (this.canvas.height/devicePixelRatio -mapHeight) / 2];
this.offset[0] = offset[0];
this.offset[1] = offset[1];
this.context.scale(devicePixelRatio, devicePixelRatio);
this.canvas.style.width = mapWidth + "px";
this.canvas.style.height = mapHeight + "px";
}
ol.source.AnimaSource.prototype.addPoint = function (f) {
let fp = new FlashPoint(f);
this.features.push(fp);
if (f.id!=null && f.id!==''){
if(this.idCahce.hasOwnProperty(f.id)){
throw new Error("id已经存在")
}
this.idCahce[f.id] = this.features.length-1
}
return fp
}
//继承 ImageCanvas
ol.inherits(ol.source.AnimaSource,ol.source.ImageCanvas);
/**
* 定义一个实体
* @param fp_options 配置信息 {}
* inSize:内圈大小-半径 ;默认 6
* outSize:外圈大小;默认 16
* color:颜色;默认 #FF0000
* opacity:透明度 [0-1] ;默认 0.3
* coord:坐标 [x,y] 地图坐标
* maxInSize:最大内圈大小;默认 10
* maxOutSize:最大外圈大小 ;默认 32
* duration:动画时长.毫秒;默认 1000
*
* */
function FlashPoint(fp_options) {
this.options = fp_options || {};
this.id = this.options.id
this.inSize = this.options.inSize || 2.5;
this.outSize = this.options.outSize || 8;
this.color = this.options.color || '#FF0000';
this.opacity = this.options.opacity || 0.3;
this.coord = this.options.coord;
this.maxInSize = this.options.maxInSize || 5;
this.maxOutSize = this.options.maxOutSize || 16;
this.duration = this.options.duration || 1000;
this.ocolor = Utils.hexToRgbOpacity(this.color,this.opacity);
this.cInSize = this.inSize;
this.cOutSize = this.outSize;
//1秒60帧
this.inStep = this.options.inStep == undefined ? (this.maxInSize - this.inSize)/this.duration*1000/60 : this.options.inStep;
this.outStep = this.options.outStep == undefined ? (this.maxOutSize - this.outSize)/this.duration*1000/60 : this.options.outStep;
this.ti = 1;
return this;
}
FlashPoint.prototype.draw = function(context,transformCoordinate){
if (this.cOutSize<=this.outSize){
this.ti = 1;
}else if(this.cOutSize>=this.maxOutSize){
this.ti = -1;
}
this.cInSize = this.cInSize+this.ti*this.inStep;
this.cOutSize = this.cOutSize+this.ti*this.outStep;
let pixel = transformCoordinate(this.coord);
context.fillStyle=this.color;
context.beginPath();
context.arc(pixel[0],pixel[1],this.cInSize,0,2*Math.PI);
context.closePath();
context.fill();
context.fillStyle=this.ocolor;
context.beginPath();
context.arc(pixel[0],pixel[1],this.cOutSize,0,2*Math.PI);
context.closePath();
context.fill();
}
使用
const animaSource = new ol.source.AnimaSource({
//配置options
})
const layer = ol.Vector.Layer({
source:animaSource
})
const flashPoint = new FlashPoint({
//options
})
animaSource.addPoint(flashPoint)
map.addLayer(layer)
也可以定义一些其它实体,实现自己的draw方法,就可以在同一个source中添加数据后渲染不同的内容。
效果图: