src/layers/canvasOverlay.js
import Overlayer from './overlay';
/**
* initCanvasOverlayer based on mapboxgl-canvas
*/
export class CanvasOverlayer extends Overlayer {
constructor(opts) {
let _opts = opts || {};
super(_opts);
this.canvas = this._init();
this.redraw = _redraw.bind(this);
// how to deconstruct opts to this if we need defaultValue.
this.labelOn = _opts.labelOn || false;
this.xfield = _opts.xfield || 'lon';
this.yfield = _opts.yfield || 'lat';
this.shadow = _opts.shadow != undefined? _opts.shadow : false;
this.lineColor = _opts.lineColor;
this.blurWidth = _opts.blurWidth != undefined? _opts.blurWidth: 4;
this.keepTrack = _opts.keepTrack != undefined? _opts.keepTrack : false;
if (this.keepTrack) {
// create trackLayer to render history track lines..
this.trackLayer = this._init();
this._initTrackCtx();
}
this.tracks = [];
this.initTrackCtx = this._initTrackCtx.bind(this);
if (_opts && _opts.map) {
this.setMap(_opts.map);
// 绑定每次move 都重绘doms..
_opts.map.on("move", () => {
this.redrawTrack();
});
}
}
_init(shadow=false,keepTrack=false) {
let canvasContainer = this.map._canvasContainer,
mapboxCanvas = this.map._canvas,
canvasOverlay = document.createElement("canvas");
canvasOverlay.style.position = "absolute";
canvasOverlay.className = "overlay-canvas";
canvasOverlay.width = parseInt(mapboxCanvas.style.width);
canvasOverlay.height = parseInt(mapboxCanvas.style.height);
canvasContainer.appendChild(canvasOverlay);
return canvasOverlay;
}
/**
* init track ctx for each track segment rendering..
*/
_initTrackCtx() {
if(this.trackLayer) {
this.trackCtx = this.trackLayer.getContext("2d");
this.movedTo = false;
initCtx(this.trackCtx, this.blurWidth,"rgba(255,255,255,.4");
this.trackCtx.lineWidth = this.lineWidth || 3;
this.trackCtx.strokeStyle = this.lineColor || "rgba(255,255,20,.6)";
this.trackCtx.beginPath();
}
}
/**
* set tracks coordinates of overlayer.
* @param {*array of track points.} tracks
*/
setTracks(tracks) {
if (Array.isArray(tracks)) {
this.tracks = tracks;
return this;
}
}
getTracks() {
return this.tracks;
}
/**
* render cached tracks to line when map moved..
*/
redrawTrack() {
if(this.trackCtx && this.tracks && this.tracks.length > 0) {
let pix = [0, 0];
this.trackCtx.clearRect(0,0,this.trackLayer.width, this.trackLayer.height);
this.trackCtx.beginPath();
pix = this.lnglat2pix(this.tracks[0][0], this.tracks[0][1]);
this.trackCtx.moveTo(pix[0], pix[1]);
for(let i = 1; i < this.tracks.length; i ++) {
pix = this.lnglat2pix(this.tracks[i][0], this.tracks[i][1]);
this.trackCtx.lineTo(pix[0], pix[1]);
}
this.trackCtx.stroke();
}
}
}
function _preSetCtx(context) {
//默认值为source-over
let prev = context.globalCompositeOperation;
//只显示canvas上原图像的重叠部分 source-in, source, destination-in
context.globalCompositeOperation = 'destination-in';
//设置主canvas的绘制透明度
context.globalAlpha = 0.95;
//这一步目的是将canvas上的图像变的透明
// context.fillStyle = "rgba(0,0,0,.95)";
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
//在原图像上重叠新图像
context.globalCompositeOperation = prev;
}
const iconSize = 32;
/**
* expoid this method, can be overwritten
* for special render requirements..
* Important ! redraw may use this.map as projector!
* @param: keepLog, keep render Sprites location log..
*/
function _redraw(objs) {
if (this.canvas) {
let ctx = this.canvas.getContext("2d");
// ctx.clearRect(0,0,canv.width, canv.height);
if (this.shadow) {
_preSetCtx(ctx);
ctx.save();
} else {
ctx.clearRect(0,0,this.canvas.width,this.canvas.height);
}
initCtx(ctx,this.blurWidth,"rgba(255,255,255,.4");
for(let i=0;i<objs.length;i++) {
let x = objs[i][this.xfield], y = objs[i][this.yfield],
radius = objs[i]['radius'] || 3, icon = objs[i]['icon'],
label = objs[i]['name'], rotate = objs[i]['direction'] || 0;
radius = Math.abs(radius);
let pix = this.lnglat2pix(x, y);
if (pix == null) continue;
ctx.fillStyle = objs[i]['color'] || 'rgba(255,240,4,.9)';
ctx.beginPath();
if (label !== undefined && label.startsWith("Play")) radius = iconSize*0.75;
// icon: ImageUrl/CanvasFunction..., clip part of img sometimes...
if (icon !== undefined) {
ctx.save();
ctx.translate(pix[0], pix[1]);
ctx.rotate(rotate*Math.PI/180);
let min = icon.height > icon.width ? icon.width : icon.height;
try {
ctx.drawImage(icon,0,0,min,min, -iconSize/2, -iconSize/2, iconSize, iconSize);
}
catch (e) {
console.warn("ctx.drawImage.. error.");
}
ctx.restore();
} else {
ctx.arc(pix[0], pix[1], radius, 0, Math.PI*2);
ctx.fill();
}
if (this.keepTrack && this.tracks.length == 0) {
this.initTrackCtx();
this.trackCtx.moveTo(pix[0],pix[1]);
this.tracks.push([x, y]);
// this.movedTo = true;
} else if (this.trackCtx) {
this.trackCtx.lineTo(pix[0],pix[1]);
this.tracks.push([x, y]);
setTimeout(()=>{
//// closePath would auto-complete the path to polygon..
this.trackCtx.stroke();
this.trackCtx.beginPath();
this.trackCtx.moveTo(pix[0],pix[1]);
}, 0);
}
if (label !== undefined && this.labelOn) {
ctx.strokeText(label, pix[0], pix[1]);
}
// ctx.closePath();
}
if(this.shadow) {
ctx.restore();
}
}
}
function initCtx(ctx, blurWidth, shadowColor="rgba(255,255,255,.8)") {
if (ctx === undefined) return;
ctx.linecap = 'round';
ctx.shadowBlur = blurWidth;
ctx.shadowColor = shadowColor;
ctx.strokeStyle = "rgba(255,255,255,.9)";
ctx.fillStyle = "rgba(255,240,91,.8)";
}
/**
* draw tri on canvas by center and rotation..
* @param rotate: degree number,
* @param radius: number, tri radius..
* /\ default beta angle is 30 degree.
* / \
* /____\
* draw triangle
*/
function drawTri(ctx, coord, rotate, radius=iconSize/2, beta=30) {
// calc the head point of triangle.
let headPoint = [undefined, undefined], tailPoint = [undefined, undefined],
rad = rotate*Math.PI/180;
headPoint[0] = coord[0] + Math.cos(rad)*radius;
headPoint[1] = coord[1] + Math.sin(rad)*radius;
tailPoint[0] = coord[0] - Math.cos(rad)*radius;
tailPoint[1] = coord[1] - Math.sin(rad)*radius;
let rot = (rotate - beta/2),
rPoint = [undefined, undefined];
rPoint[0] = Math.cos(rot*Math.PI/180);
ctx.lineTo(headPoint);
}