概述:javascript
本文講述如何結合canvas在Openlayers4中實現動態線的效果。css
效果:java
代碼:git
一、move-line擴展github
[javascript] view plain copyweb
- (function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
- typeof define === 'function' && define.amd ? define(factory) :
- (global.MoveLine = factory());
- }(this, (function () { 'use strict';
-
- /**
- * @author https://github.com/chengquan223
- * @Date 2017-02-27
- * */
- function CanvasLayer(options) {
- this.options = options || {};
- this.paneName = this.options.paneName || 'labelPane';
- this.zIndex = this.options.zIndex || 0;
- this._map = options.map;
- this._lastDrawTime = null;
- this.show();
- }
-
- CanvasLayer.prototype.initialize = function () {
- var canvas = this.canvas = document.createElement('canvas');
- var ctx = this.ctx = this.canvas.getContext('2d');
- canvas.style.cssText = 'position:absolute;' + 'left:0;' + 'top:0;' + 'z-index:' + this.zIndex + ';';
- this.adjustSize();
- this.adjustRatio(ctx);
- map.getViewport().appendChild(canvas);
- var that = this;
- map.getView().on('propertychange',function(){
- $(canvas).hide();
- });
- map.on("moveend",function(){
- $(canvas).show();
- that.adjustSize();
- that._draw();
- });
- return this.canvas;
- };
-
- CanvasLayer.prototype.adjustSize = function () {
- var size = this._map.getSize();
- var canvas = this.canvas;
- canvas.width = size[0];
- canvas.height = size[1];
- canvas.style.width = canvas.width + 'px';
- canvas.style.height = canvas.height + 'px';
- };
-
- CanvasLayer.prototype.adjustRatio = function (ctx) {
- var backingStore = ctx.backingStorePixelRatio || ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || ctx.oBackingStorePixelRatio || ctx.backingStorePixelRatio || 1;
- var pixelRatio = (window.devicePixelRatio || 1) / backingStore;
- var canvasWidth = ctx.canvas.width;
- var canvasHeight = ctx.canvas.height;
- ctx.canvas.width = canvasWidth * pixelRatio;
- ctx.canvas.height = canvasHeight * pixelRatio;
- ctx.canvas.style.width = canvasWidth + 'px';
- ctx.canvas.style.height = canvasHeight + 'px';
- ctx.scale(pixelRatio, pixelRatio);
- };
-
- CanvasLayer.prototype.draw = function () {
- var self = this;
- var args = arguments;
- clearTimeout(self.timeoutID);
- self.timeoutID = setTimeout(function () {
- self._draw();
- }, 15);
- };
-
- CanvasLayer.prototype._draw = function () {
- var map = this._map;
- var size = map.getSize();
- var center = map.getView().getCenter();
- if (center) {
- var pixel = map.getPixelFromCoordinate(center);
- this.canvas.style.left = pixel[0] - size[0] / 2 + 'px';
- this.canvas.style.top = pixel[1] - size[1] / 2 + 'px';
- this.options.update && this.options.update.call(this);
- }
- };
-
- CanvasLayer.prototype.getContainer = function () {
- return this.canvas;
- };
-
- CanvasLayer.prototype.show = function () {
- this.initialize();
- this.canvas.style.display = 'block';
- };
-
- CanvasLayer.prototype.hide = function () {
- this.canvas.style.display = 'none';
- };
-
- CanvasLayer.prototype.setZIndex = function (zIndex) {
- this.canvas.style.zIndex = zIndex;
- };
-
- CanvasLayer.prototype.getZIndex = function () {
- return this.zIndex;
- };
-
- var global = typeof window === 'undefined' ? {} : window;
-
- var requestAnimationFrame = global.requestAnimationFrame || global.mozRequestAnimationFrame || global.webkitRequestAnimationFrame || global.msRequestAnimationFrame || function (callback) {
- return global.setTimeout(callback, 1000 / 60);
- };
-
- var MoveLine = function MoveLine(map, userOptions) {
- var self = this;
-
- //默認參數
- var options = {
- //marker點半徑
- markerRadius: 3,
- //marker點顏色,爲空或null則默認取線條顏色
- markerColor: '#fff',
- //線條類型 solid、dashed、dotted
- lineType: 'solid',
- //線條寬度
- lineWidth: 1,
- //線條顏色
- colors: ['#F9815C', '#F8AB60', '#EDCC72', '#E2F194', '#94E08A', '#4ECDA5'],
- //移動點半徑
- moveRadius: 2,
- //移動點顏色
- fillColor: '#fff',
- //移動點陰影顏色
- shadowColor: '#fff',
- //移動點陰影大小
- shadowBlur: 5
- };
-
- //全局變量
- var baseLayer = null,
- animationLayer = null,
- width = map.getSize()[0],
- height = map.getSize()[1],
- animationFlag = true,
- markLines = [];
-
- //參數合併
- var merge = function merge(userOptions, options) {
- Object.keys(userOptions).forEach(function (key) {
- options[key] = userOptions[key];
- });
- };
-
- function Marker(opts) {
- this.city = opts.city;
- this.location = opts.location;
- this.color = opts.color;
- }
-
- Marker.prototype.draw = function (context) {
- var pixel = this.pixel = map.getPixelFromCoordinate(this.location);
-
- context.save();
- context.beginPath();
- context.fillStyle = options.markerColor || this.color;
- context.arc(pixel[0], pixel[1], options.markerRadius, 0, Math.PI * 2, true);
- context.closePath();
- context.fill();
-
- context.textAlign = 'center';
- context.textBaseline = 'middle';
- context.font = '12px Microsoft YaHei';
- context.fillStyle = this.color;
- context.fillText(this.city, pixel[0], pixel[1] - 10);
- context.restore();
- };
-
- function MarkLine(opts) {
- this.from = opts.from;
- this.to = opts.to;
- this.id = opts.id;
- this.step = 0;
- }
-
- MarkLine.prototype.getPointList = function (from, to) {
- var points = [[from[0], from[1]], [to[0], to[1]]];
- var ex = points[1][0];
- var ey = points[1][1];
- points[3] = [ex, ey];
- points[1] = this.getOffsetPoint(points[0], points[3]);
- points[2] = this.getOffsetPoint(points[3], points[0]);
- points = this.smoothSpline(points, false);
- //修正最後一點在插值產生的偏移
- points[points.length - 1] = [ex, ey];
- return points;
- };
-
- MarkLine.prototype.getOffsetPoint = function (start, end) {
- var distance = this.getDistance(start, end) / 3; //除以3?
- var angle, dX, dY;
- var mp = [start[0], start[1]];
- var deltaAngle = -0.2; //偏移0.2弧度
- if (start[0] != end[0] && start[1] != end[1]) {
- //斜率存在
- var k = (end[1] - start[1]) / (end[0] - start[0]);
- angle = Math.atan(k);
- } else if (start[0] == end[0]) {
- //垂直線
- angle = (start[1] <= end[1] ? 1 : -1) * Math.PI / 2;
- } else {
- //水平線
- angle = 0;
- }
- if (start[0] <= end[0]) {
- angle -= deltaAngle;
- dX = Math.round(Math.cos(angle) * distance);
- dY = Math.round(Math.sin(angle) * distance);
- mp[0] += dX;
- mp[1] += dY;
- } else {
- angle += deltaAngle;
- dX = Math.round(Math.cos(angle) * distance);
- dY = Math.round(Math.sin(angle) * distance);
- mp[0] -= dX;
- mp[1] -= dY;
- }
- return mp;
- };
-
- MarkLine.prototype.smoothSpline = function (points, isLoop) {
- var len = points.length;
- var ret = [];
- var distance = 0;
- for (var i = 1; i < len; i++) {
- distance += this.getDistance(points[i - 1], points[i]);
- }
- var segs = distance / 2;
- segs = segs < len ? len : segs;
- for (var i = 0; i < segs; i++) {
- var pos = i / (segs - 1) * (isLoop ? len : len - 1);
- var idx = Math.floor(pos);
- var w = pos - idx;
- var p0;
- var p1 = points[idx % len];
- var p2;
- var p3;
- if (!isLoop) {
- p0 = points[idx === 0 ? idx : idx - 1];
- p2 = points[idx > len - 2 ? len - 1 : idx + 1];
- p3 = points[idx > len - 3 ? len - 1 : idx + 2];
- } else {
- p0 = points[(idx - 1 + len) % len];
- p2 = points[(idx + 1) % len];
- p3 = points[(idx + 2) % len];
- }
- var w2 = w * w;
- var w3 = w * w2;
-
- ret.push([this.interpolate(p0[0], p1[0], p2[0], p3[0], w, w2, w3), this.interpolate(p0[1], p1[1], p2[1], p3[1], w, w2, w3)]);
- }
- return ret;
- };
-
- MarkLine.prototype.interpolate = function (p0, p1, p2, p3, t, t2, t3) {
- var v0 = (p2 - p0) * 0.5;
- var v1 = (p3 - p1) * 0.5;
- return (2 * (p1 - p2) + v0 + v1) * t3 + (-3 * (p1 - p2) - 2 * v0 - v1) * t2 + v0 * t + p1;
- };
-
- MarkLine.prototype.getDistance = function (p1, p2) {
- return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
- };
-
- MarkLine.prototype.drawMarker = function (context) {
- this.from.draw(context);
- this.to.draw(context);
- };
-
- MarkLine.prototype.drawLinePath = function (context) {
- var pointList = this.path = this.getPointList(map.getPixelFromCoordinate(this.from.location), map.getPixelFromCoordinate(this.to.location));
- var len = pointList.length;
- context.save();
- context.beginPath();
- context.lineWidth = options.lineWidth;
- context.strokeStyle = options.colors[this.id];
-
- if (!options.lineType || options.lineType == 'solid') {
- context.moveTo(pointList[0][0], pointList[0][1]);
- for (var i = 0; i < len; i++) {
- context.lineTo(pointList[i][0], pointList[i][1]);
- }
- } else if (options.lineType == 'dashed' || options.lineType == 'dotted') {
- for (var i = 1; i < len; i += 2) {
- context.moveTo(pointList[i - 1][0], pointList[i - 1][1]);
- context.lineTo(pointList[i][0], pointList[i][1]);
- }
- }
- context.stroke();
- context.restore();
- this.step = 0; //縮放地圖時從新繪製動畫
- };
-
- MarkLine.prototype.drawMoveCircle = function (context) {
- var pointList = this.path || this.getPointList(map.getPixelFromCoordinate(this.from.location), map.getPixelFromCoordinate(this.to.location));
-
- context.save();
- context.fillStyle = options.fillColor;
- context.shadowColor = options.shadowColor;
- context.shadowBlur = options.shadowBlur;
- context.beginPath();
- context.arc(pointList[this.step][0], pointList[this.step][1], options.moveRadius, 0, Math.PI * 2, true);
- context.fill();
- context.closePath();
- context.restore();
- this.step += 1;
- if (this.step >= pointList.length) {
- this.step = 0;
- }
- };
-
- //底層canvas渲染,標註,線條
- var brush = function brush() {
- var baseCtx = baseLayer.canvas.getContext('2d');
- if (!baseCtx) {
- return;
- }
-
- addMarkLine();
-
- baseCtx.clearRect(0, 0, width, height);
-
- markLines.forEach(function (line) {
- line.drawMarker(baseCtx);
- line.drawLinePath(baseCtx);
- });
- };
-
- //上層canvas渲染,動畫效果
- var render = function render() {
- var animationCtx = animationLayer.canvas.getContext('2d');
- if (!animationCtx) {
- return;
- }
-
- if (!animationFlag) {
- animationCtx.clearRect(0, 0, width, height);
- return;
- }
-
- animationCtx.fillStyle = 'rgba(0,0,0,.93)';
- var prev = animationCtx.globalCompositeOperation;
- animationCtx.globalCompositeOperation = 'destination-in';
- animationCtx.fillRect(0, 0, width, height);
- animationCtx.globalCompositeOperation = prev;
-
- for (var i = 0; i < markLines.length; i++) {
- var markLine = markLines[i];
- markLine.drawMoveCircle(animationCtx); //移動圓點
- }
- };
- var addMarkLine = function addMarkLine() {
- markLines = [];
- var dataset = options.data;
- dataset.forEach(function (line, i) {
- markLines.push(new MarkLine({
- id: i,
- from: new Marker({
- city: line.from.city,
- location: [line.from.lnglat[0], line.from.lnglat[1]],
- color: options.colors[i]
- }),
- to: new Marker({
- city: line.to.city,
- location: [line.to.lnglat[0], line.to.lnglat[1]],
- color: options.colors[i]
- })
- }));
- });
- };
-
- //初始化
- var init = function init(map, options) {
- merge(userOptions, options);
-
- baseLayer = new CanvasLayer({
- map: map,
- update: brush
- });
-
- animationLayer = new CanvasLayer({
- map: map,
- update: render
- });
-
- (function drawFrame() {
- requestAnimationFrame(drawFrame);
- render();
- })();
- };
-
- init(map, options);
-
- self.options = options;
- };
-
- MoveLine.prototype.update = function (resetOpts) {
- for (var key in resetOpts) {
- this.options[key] = resetOpts[key];
- }
- };
-
- return MoveLine;
-
- })));
二、前段調用canvas
a、數據格式app
[javascript] view plain copyide
- {
- from: {
- city: '廣州',
- lnglat: [113.270793, 23.135308]
- },
- to: {
- city: '衡山',
- lnglat: [112.612787, 27.317599]
- }
- }
b、前段調用oop
[javascript] view plain copy
- new MoveLine(map, {
- //marker點半徑
- markerRadius: 2,
- //marker點顏色,爲空或null則默認取線條顏色
- markerColor: null,
- //線條類型 solid、dashed、dotted
- lineType: 'solid',
- //線條寬度
- lineWidth: 2,
- //線條顏色
- colors: ['#F9815C', '#F8AB60', '#EDCC72', '#E2F194', '#94E08A', '#4ECDA5'],
- //移動點半徑
- moveRadius: 3,
- //移動點顏色
- fillColor: '#fff',
- //移動點陰影顏色
- shadowColor: '#fff',
- //移動點陰影大小
- shadowBlur: 6,
- data: data
- });
------------------------------------------------------------------------------https://blog.csdn.net/GISShiXiSheng/article/details/78148576?locationNum=11&fps=1