背景

  前段时间工作室接到一个与地图相关的项目,我作为项目组成员主要负责地图方面的设计和开发。由于地图部分主要涉及的是前端页面的显示,作为一名Java后端的小白,第一次写了这么多HTML和JavaScript。

  项目大概是需要将一张CAD的图(导出大概三十万条数据)叠加在地图上,在接Canvas之前考虑了很多种方案,最后都否定了。首先我们想利用百度地图原生的JavaScript API实现线和点的加载,但是经过测试,当数据达到2000左右,加载时间就已经达到了数十秒,后来直接测试了一万条数据,浏览器直接卡死了,这种方案很快就被否定了。然后我们又准备采用分割静态图的方法,将整个CAD图分割成地图瓦片,作为覆盖图层叠加在原来地图之上,在这一步中,由于CAD图的信息涉及到整个城市,信息量非常巨大,几乎没有找到合适软件能够导出一张这么大的图。后来仔细研究需求文档后又发现需要针对图的信息做操作,然后这种方案将近完成的时候被否定了。最后,偶然在Github上看到:https://github.com/lcosmos/map-canvas 这个实现台风轨迹,这个数据量非常庞大,当时打开时,看到这么多数据加载很快,感到有点震惊,然后自己研究了一番,发现作者采用的是Canvas作为百度的自定义覆盖层,说干就干,自己尝试写出了第一个版本,写上Ajax请求,效果十分震撼,将近秒级加载,加了计时器测试了一番性能,发现绘画只花了1ms左右,主要延时都在请求延时(90M左右的数据),内存占用也非常少,这下放心多了,项目基本也可以完成了,然后当然是捣鼓传输延时,GZip等都用上了,最后也有了极大优化,客户看后觉得还十分不错。

系列文章:

CAD数据分块校准

效果图

(由于项目涉及国家机密,部分细节和图片不便于展示,但已经可以完成标题需求)

具体实现

(由于项目涉及国家机密,部分细节和图片不便于展示,但已经可以完成标题需求)

HTML测试页:

<!DOCTYPE html>
<html>
<head>
<title>百度地图Canvas海量折线</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
<link rel="stylesheet" href="css/style.css">
<script type="text/javascript" src="https://api.map.baidu.com/api?v=2.0&ak=nuWah68S1WieW2AEwiT8T3Ro&s=1"></script>
<script type="text/javascript" src="js/jquery.min.js"></script>
</head>
<body>
<div id="map"></div>
<script type="text/javascript" src="pointLine.js"></script>
<script type="text/javascript">
var map = new BMap.Map('map', {
minZoom: 5
});
map.centerAndZoom(new BMap.Point(112.954699, 27.851256), 13);
map.enableScrollWheelZoom(true);
map.setMapStyle({
styleJson: styleJson
});
$.getJSON('line.json', function (result) {
var pointLine = new PointLine(map, {
//线条宽度
lineWidth: 2,
//线条颜色
lineStyle: '#F9815C',
//数据源
data: result,
//事件
methods: {
click: function (e, name) {
console.log('你当前点击的是' + name);
},
// mousemove: function (e, name) {
// console.log('你当前点击的是' + name);
// }
}
});
})
</script>
</body>
</html>

核心JavaScript:pointLine.js

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.PointLine = factory());
}(this, (function () { 'use strict'; 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 = new BMap.Overlay(); CanvasLayer.prototype.initialize = function (map) {
this._map = map;
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.getPanes()[this.paneName].appendChild(canvas);
var that = this;
map.addEventListener('resize', function () {
that.adjustSize();
that._draw();
});
return this.canvas;
}; CanvasLayer.prototype.adjustSize = function () {
var size = this._map.getSize();
var canvas = this.canvas;
canvas.width = size.width;
canvas.height = size.height;
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';
// console.log(ctx.canvas.height, canvasHeight);
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.getCenter();
if (center) {
var pixel = map.pointToOverlayPixel(center);
this.canvas.style.left = pixel.x - size.width / 2 + 'px';
this.canvas.style.top = pixel.y - size.height / 2 + 'px';
this.dispatchEvent('draw');
this.options.update && this.options.update.call(this);
}
}; CanvasLayer.prototype.getContainer = function () {
return this.canvas;
}; CanvasLayer.prototype.show = function () {
if (!this.canvas) {
this._map.addOverlay(this);
}
this.canvas.style.display = 'block';
}; CanvasLayer.prototype.hide = function () {
this.canvas.style.display = 'none';
//this._map.removeOverlay(this);
}; CanvasLayer.prototype.setZIndex = function (zIndex) {
this.canvas.style.zIndex = zIndex;
}; CanvasLayer.prototype.getZIndex = function () {
return this.zIndex;
}; var tool = {
merge: function merge(settings, defaults) {
Object.keys(settings).forEach(function (key) {
defaults[key] = settings[key];
});
},
//计算两点间距离
getDistance: function getDistance(p1, p2) {
return Math.sqrt((p1[0] - p2[0]) * (p1[0] - p2[0]) + (p1[1] - p2[1]) * (p1[1] - p2[1]));
},
//判断点是否在线段上
containStroke: function containStroke(x0, y0, x1, y1, lineWidth, x, y) {
if (lineWidth === 0) {
return false;
}
var _l = lineWidth;
var _a = 0;
var _b = x0;
// Quick reject
if (y > y0 + _l && y > y1 + _l || y < y0 - _l && y < y1 - _l || x > x0 + _l && x > x1 + _l || x < x0 - _l && x < x1 - _l) {
return false;
} if (x0 !== x1) {
_a = (y0 - y1) / (x0 - x1);
_b = (x0 * y1 - x1 * y0) / (x0 - x1);
} else {
return Math.abs(x - x0) <= _l / 2;
}
var tmp = _a * x - y + _b;
var _s = tmp * tmp / (_a * _a + 1);
return _s <= _l / 2 * _l / 2;
}
}; var PointLine = function PointLine(map, userOptions) {
var self = this; self.map = map;
self.lines = [];
self.pixelList = []; //默认参数
var options = {
//线条宽度
lineWidth: 1,
//线条颜色
lineStyle: '#F9815C'
}; //全局变量
var baseLayer = null,
width = map.getSize().width,
height = map.getSize().height; function Line(opts) {
this.name = opts.name;
this.path = opts.path;
} Line.prototype.getPointList = function () {
var points = [],
path = this.path;
if (path && path.length > 0) {
path.forEach(function (p) {
points.push({
name: p.name,
pixel: map.pointToPixel(p.location)
});
});
}
return points;
}; Line.prototype.draw = function (context) {
var pointList = this.pixelList || this.getPointList();
context.save();
context.beginPath();
context.lineWidth = options.lineWidth;
context.strokeStyle = options.lineStyle;
context.moveTo(pointList[0].pixel.x, pointList[0].pixel.y);
for (var i = 0, len = pointList.length; i < len; i++) {
context.lineTo(pointList[i].pixel.x, pointList[i].pixel.y);
}
context.stroke();
context.closePath();
context.restore();
}; //底层canvas渲染,标注,线条
var brush = function brush() {
var baseCtx = baseLayer.canvas.getContext('2d');
if (!baseCtx) {
return;
} addLine(); baseCtx.clearRect(0, 0, width, height); self.pixelList = [];
self.lines.forEach(function (line) {
self.pixelList.push({
name: line.name,
data: line.getPointList()
});
line.draw(baseCtx);
});
}; var addLine = function addLine() {
if (self.lines && self.lines.length > 0) return;
var dataset = options.data;
dataset.forEach(function (l, i) {
var line = new Line({
name: l.name,
path: []
});
l.data.forEach(function (p, j) {
line.path.push({
name: p.name,
location: new BMap.Point(p.Longitude, p.Latitude)
});
});
self.lines.push(line);
});
}; self.init(userOptions, options); baseLayer = new CanvasLayer({
map: map,
update: brush
}); this.clickEvent = this.clickEvent.bind(this); this.bindEvent();
}; PointLine.prototype.init = function (settings, defaults) {
//合并参数
tool.merge(settings, defaults); this.options = defaults;
}; PointLine.prototype.bindEvent = function (e) {
var map = this.map;
if (this.options.methods) {
if (this.options.methods.click) {
map.setDefaultCursor("default");
map.addEventListener('click', this.clickEvent);
}
if (this.options.methods.mousemove) {
map.setDefaultCursor("default");
map.addEventListener('mousemove', this.clickEvent);
}
}
}; PointLine.prototype.clickEvent = function (e) {
var self = this,
lines = self.pixelList;
if (lines.length > 0) {
lines.forEach(function (line, i) {
for (var j = 0; j < line.data.length; j++) {
var beginPt = line.data[j].pixel;
if (line.data[j + 1] == undefined) {
return;
}
var endPt = line.data[j + 1].pixel;
var curPt = e.pixel;
var isOnLine = tool.containStroke(beginPt.x, beginPt.y, endPt.x, endPt.y, self.options.lineWidth, curPt.x, curPt.y);
if (isOnLine) {
self.options.methods.click(e, line.name);
return;
}
}
});
}
}; return PointLine; })));

测试数据:line.json

https://files.cnblogs.com/files/lcosmos/line.json.zip

百度地图Canvas实现十万CAD数据秒级加载的更多相关文章

  1. 【 转】百度地图Canvas实现十万CAD数据秒级加载

    Github上看到: https://github.com/lcosmos/map-canvas 这个实现台风轨迹,这个数据量非常庞大,当时打开时,看到这么多数据加载很快,感到有点震惊,然后自己研究了 ...

  2. C#的百度地图开发(二)转换JSON数据为相应的类

    原文:C#的百度地图开发(二)转换JSON数据为相应的类 在<C#的百度地图开发(一)发起HTTP请求>一文中我们向百度提供的API的URL发起请求,并得到了返回的结果,结果是一串JSON ...

  3. SQL Server大量数据秒级插入/新增/删除

    原文:SQL Server大量数据秒级插入/新增/删除 1.快速保存,该方法有四个参数,第一个参数为数据库连接,第二个参数为需要保存的DataTable,该参数的TableName属性需要设置为数据库 ...

  4. Redis实战--使用Jedis实现百万数据秒级插入

    echo编辑整理,欢迎转载,转载请声明文章来源.欢迎添加echo微信(微信号:t2421499075)交流学习. 百战不败,依不自称常胜,百败不颓,依能奋力前行.--这才是真正的堪称强大!!! 当我们 ...

  5. DataTable 删除数据后重新加载

    DataTable 删除数据后重新加载 一.总结 一句话总结: 判断datatable是否被datatable初始化或者是否执行了datatable销毁函数,如果没有,就销毁它 if ($('#dat ...

  6. 【EF学习笔记08】----------加载关联表的数据 显式加载

    显式加载 讲解之前,先来看一下我们的数据库结构:班级表 学生表 加载从表集合类型 //显示加载 Console.WriteLine("=========查询集合===========&quo ...

  7. vue 中监测滚动条加载数据(懒加载数据)

    vue 中监测滚动条加载数据(懒加载数据) 1:钩子函数监听滚动事件: mounted () { this.$nextTick(function () { window.addEventListene ...

  8. OGG初始化之将数据从文件加载到Replicat

    要使用Replicat建立目标数据,可以使用初始加载Extract从源表中提取源记录,并将它们以规范格式写入提取文件.从该文件中,初始加载Replicat使用数据库接口加载数据.在加载过程中,更改同步 ...

  9. hibernate框架学习之数据抓取(加载)策略

    Hibernate获取数据方式 lHibernate提供了多种方式获取数据 •load方法获取数据 •get方法获取数据 •Query/ Criteria对象获取数据 lHibernate获取的数据分 ...

随机推荐

  1. javascript获取指定区间范围随机数

    //获取指定区间范围随机数,包括lowerValue和upperValuefunction randomFrom(lowerValue,upperValue){    return Math.floo ...

  2. jQuery写toTop(回到顶部)效果

    在通常的网站开发中,页面有时候会很长,尤其是一些电商网站,为了提高用户的体验效果,我们通常会增加一个回到顶部的按钮,这个按钮我们同城会使用fixed定位,将其定位在当前可视区域某一固定位置.这个效果用 ...

  3. Git 从master拉取代码创建新分支

    从master拉取新分支并push到远端 开发过程中经常用到从master分支copy一个开发分支: 1.切换到被copy的分支(master),并且从远端拉取最新版本 $git checkout m ...

  4. http协议(一):http协议基础知识

    1    协议类型 l  HTTP  超文本传输协议 通过浏览器和服务器进行数据交互,进行超文本(文本.图片.视频等)传输的规定 l  HTTPS 安全超文本传输协议 l  FTP 文本传输协议 l  ...

  5. Netty服务端启动过程相关源码分析

    1.Netty 是怎么创建服务端Channel的呢? 我们在使用ServerBootstrap.bind(端口)方法时,最终调用其父类AbstractBootstrap中的doBind方法,相关源码如 ...

  6. 5.Go-封装、继承、接口、多态和断言

    面向对象 Go语言开发者认为:面向对象就是特定类型(结构体)有着自己的方法,利用这个方法完成面向对象编程, 并没有提封装.继承.多态.所以Go语言进行面向对象编程时,重点在于灵活使用方法. Go语言有 ...

  7. Windows 下安装 Python + Django

    Django是Python的一个Web开发框架,以下是介绍的是windows下的安装步骤, 作者的环境是Win10 ,Windows Server 也是一样的 以下是作者整理的步骤,也可以参考官方教程 ...

  8. 有趣的RPC理解

    RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议.RPC协议假定某些传输协议的存在,如TCP或UDP,为通 ...

  9. java并发编程(十三)----(JUC原子类)引用类型介绍(CAS和ABA的介绍)

    这一节我们将探讨引用类型原子类:AtomicReference, AtomicStampedRerence, AtomicMarkableReference.AtomicReference的使用非常简 ...

  10. Markdown 基本语法(后面继续补充)

    1.1 Markdown 基础语法 有序内容和无序内容 有序内容:输入1.然后按tab键 无序内容:输入' * ' 或 ' - ' 然后后按tab键 字体的样式 *** 内容 *** 加粗加斜(中间没 ...