1. 需求

有包含30万坐标点的json文件,每个坐标点包含经度、纬度、行值、列值、数值,现需要根据数值分级进行不同颜色的显示,并在地图的正确位置进行渲染。最终效果如下:

2. 环境和工具

2.1 使用Edge、Chrome

实测采用Chromium内核的浏览器在坐标计算和Canvas渲染速度上要快出非常多(对比Firefox),下图前7列为Firefox,最后一列为Edge,同样为7万个点,快了一倍不止。

2.2 PixJS的使用

Pixjs:HTML5创建引擎。最快、最灵活的 2D WebGL 渲染器。

Pixjs是一个2D的游戏引擎,它真的很快,非常快。本文只用到了最基础的两个功能,矩形绘制和图片绘制。渲染30万个矩形平均时间在400ms左右。

PixJS官网:https://pixijs.com/

PixJS手册中文网站:https://aitrade.ga/pixi.js-cn/index.html

3. 实现思路

3.1 地图引擎分析

Canvas绘图技术在地图上实现数据渲染,无非要解决两个问题。一是拖动跟随,二是缩放重绘。不同地图引擎在地图绘制上使用的技术和方法不同,比如Leaflet使用SVG,而ArcGIS使用WebGL。同时,他们对Canvas图层的处理方式也不同。

以Leaflet为例,在地图加载完成后,手动添加Canvas图层,并且在左上角绘制一个矩形。

当我们拖动地图一段距离后再次观察canvas的位置,发现它的定位并没有发生变化,但是却不在屏幕中心了。说明它跟随父级div的位置变动而变动,且起点坐标位于屏幕左上角。

了解canvas的变换规律,可以让我们在定位图片的过程中有的放矢。

3.2 渲染方式选择

一种方式是直接将栅格渲染至canvas中,在地图缩放后重新渲染。另一种方式是,先在离屏canvas中渲染出需要的效果,保存为webp格式或png格式的图片,之后的处理都在该图片上进行。这里采用方式二。

3.3 思路梳理

  • 初始化地图(监听地图移动事件)
  • 初始化PixJS(添加canvas)
  • 生成webp格式图片
  • 在地图上正确定位webp图
  • 响应地图拖动和缩放

4. 编码实现

4.1 监听地图事件

map.on('moveend', () => {// 获取地图拖动后,相对于地图原点坐标原点的偏移值const offset_x = map._mapPane._leaflet_pos.x;const offset_y = map._mapPane._leaflet_pos.y;// 获取地图相对于上次的偏移值‘,此时this.offset_x是上次的偏移值// 首次偏移后,偏移值‘ = 相对于地图原点坐标原点的偏移值if (this.offset_x) {this.offset_x_count = offset_x - this.offset_x;} else {this.offset_x_count = offset_x;}if (this.offset_y) {this.offset_y_count = offset_y - this.offset_y;} else {this.offset_y_count = offset_y;}// 记录本次偏移值this.offset_x = offset_x;this.offset_y = offset_y;
})

需要在地图初始化之后便开始记录偏移量(地图的拖动),map._mapPane._leaflet_pos.x获取的是始终是地图相对于初始化状态的偏移量,因此每次移动后的偏移增量需要额外计算。

4.2 初始化PixiJS

initPixjs() {let left = -this.map._mapPane._leaflet_pos.x + 'px';let top = -this.map._mapPane._leaflet_pos.y + 'px';let app = new PIXI.Application({width: document.body.clientWidth,height: document.body.clientHeight,backgroundAlpha: 0});this.pixjsApp = app;this.canvas = app.view;this.canvas.style.position = "absolute";this.canvas.style.top = top;this.canvas.style.left = left;let parent = document.getElementsByClassName("leaflet-pane leaflet-overlay-pane")[0];parent.appendChild(app.view);
}

pixi会自动生成canvas元素,我们需要将其添加到父级元素之下。在添加时,地图可能未移动过,也可能移动过,为了保证canvas起点位于屏幕左上角,在添加前获取地图的偏移值并将其负值作为canvas的绝对定位值。

4.3 经纬度转屏幕坐标

Leaflet并没有现成的方法将一个经纬度坐标转换到屏幕坐标,但是从文章:Leaflet 如何把一个坐标转换到屏幕上中我们可以得知,Leaflet 加载完以后,会有一个 map-pane 的div元素,里面包含了所有的图层,PixelOrigin 就是 map-pane 这个容器最左上角的位置。我们可以将某个经纬度转换为投影坐标,再减去像素原点的坐标,就能得到该点在屏幕中的具体位置,这也是为什么我们需要把canvas元素的起点固定在屏幕左上角,如此一来计算出来的屏幕坐标就是在canvas中的坐标。

/**
* 经纬度转屏幕坐标
* @param lon 经度
* @param lat 纬度
* @param zoom 缩放等级
* @returns {{x: number, y: number}} 屏幕坐标
*/
lngLatToScreen(lon, lat, zoom) {let point = this.$refs.map.CRS_4490.latLngToPoint(L.latLng({lon: lon, lat: lat}), zoom);let origin = this.$refs.map.map.getPixelOrigin();return {x: (point.x - origin.x),y: (point.y - origin.y)}
}
/**
* 全部栅格点坐标转换
* @param points 栅格点(行,列,积水值)
* @param bounds 显示范围
* @param zoom
*/
pointsToScreen(points, bounds, zoom) {const screen_left_bottom = this.lngLatToScreen(bounds.left_bottom[0], bounds.left_bottom[1], zoom);const screen_right_top = this.lngLatToScreen(bounds.right_top[0], bounds.right_top[1], zoom);// 计算每行/列总像素值,保存,作为渲染时画布的大小let row_pixels = (screen_right_top.x - screen_left_bottom.x);let col_pixels = (screen_left_bottom.y - screen_right_top.y);// 计算行像素步长和列像素步长let step_x = row_pixels / bounds.col_count; //在canvas的一行中,也就是x轴方向,每个栅格应该占有的像素 = 一行的总像素/总列数let step_y = col_pixels / bounds.row_count; //在canvas的一列中,也就是y轴方向,每个栅格应该占有的像素 = 一列的总像素/总行数// 渲染起点坐标let origin_x = screen_left_bottom.x;let origin_y = screen_right_top.y;this.origin_x = origin_x;this.origin_y = origin_y;// 计算所有栅格点相对于起点,在canvas中的渲染坐标let canvas_points = {};for (let i = 1; i <= 9; i++) {let part_result = [];let part_points = points['level' + i];for (let point of part_points) {// 行数确定y轴坐标,列数确定x轴坐标let x = point[1] * step_x;let y = (bounds.row_count - point[0] - 1) * step_y;part_result.push([x, y]);}canvas_points['level' + i] = part_result;}return {canvas_points: canvas_points,render_width: row_pixels,render_height: col_pixels,pixel_width: step_x,pixel_height: step_y,canvas_width: screen_right_top.x,canvas_height: screen_left_bottom.y,origin_x: origin_x,origin_y: origin_y}
},

坐标文件格式不同,处理的步骤便不同。这里主要关注以下3个部分:

  1. 计算出渲染范围,左下角和右上角屏幕坐标
  2. 计算出每一个坐标的渲染起点
  3. 计算出每一个坐标的渲染宽高

4.4 生成webp图

/**
* 生成离屏图webp图
* 在拖动时只需渲染webp图,缩放等级变化后需要重新生成
* @param zoom 缩放等级
* @param offset_x 地图x轴偏移量
* @param offset_y 地图y轴偏移量
* @returns webp图 Base64格式
*/
generatePic(zoom, offset_x, offset_y) {let result = this.pointsToScreen(this.points, this.bounds, zoom);let canvas_points = result['canvas_points'];let render_width = result['render_width'];let render_height = result['render_height'];let pixel_width = result['pixel_width'];let pixel_height = result['pixel_height'];let origin_x = result['origin_x'];let origin_y = result['origin_y'];const app = new PIXI.Application({width: render_width,height: render_height,backgroundAlpha: 0,preserveDrawingBuffer: true,autoDensity: true,autoStart: true,});const graphic = new PIXI.Graphics();app.stage.addChild(graphic);// console.time('渲染计时');for (let i = 1; i <= 9; i++) {let part_points = canvas_points['level' + i];graphic.beginFill(this.colors[i]);part_points.forEach(point => {graphic.drawRect(point[0], point[1], pixel_width, pixel_height)});}app.render();// console.timeEnd('渲染计时');this.imageBase64 = app.view.toDataURL('image/webp', 1.0);// 将图片加载到地图上this.addPicToMap(this.imageBase64, origin_x, origin_y, offset_x, offset_y);// pixjs使用后,销毁webgl_contentapp.renderer.gl.getExtension('WEBGL_lose_context').loseContext();app.destroy({removeView: true,stageOptions: {children: true}});return this.imageBase64;
}

这里使用PixiJS的Graphic类来绘制所有的矩形,这里的绘制并没有在屏幕中显示,而是在内存中进行。完成绘制后将canvas中的图像保存为base64编码,图片选择webp格式,相对于png格式它的文件体积更小,同时画质差距不大。离屏图的效果如下:

4.5 将webp图添加到地图中

addPicToMap(image_base64, origin_x, origin_y, offset_x, offset_y) {const pic = PIXI.Sprite.from(image_base64);pic.x = origin_x + offset_x;pic.y = origin_y + offset_y;this.pixjsPic = pic;this.pixjsApp.stage.addChild(pic);
}

前面我们提到,在添加图片之前,地图就可能移动过,因此图片在canvas中的位置也需要考虑地图的偏移,offset_x, offset_y参数在调用4.4函数时传入,其数值是当前地图偏移量。

4.6 响应地图事件

/*** 更新图片在canvas中的位置
* @param pic pixjs的Sprite类 包含一张图片
* @param x x轴偏移量
* @param y y轴偏移量
*/
changePicPosition(pic, x, y) {// 根据地图偏移数值设定图片位置pic.x = pic.x + x;pic.y = pic.y + y;
}
map.on('moveend', () => {if (!this.pixjsPic) {return;}// 1.若缩放等级变化,根据现有坐标点,重新生成图片并加载到地图上let sub = this.zoom - map.getZoom()if (sub !== 0) {this.generatePic(map.getZoom(), map._mapPane._leaflet_pos.x, map._mapPane._leaflet_pos.y);this.zoom = map.getZoom();return;}// 保持canvas在屏幕中央// canvas相对于图层的的绝对定位值变化方向,是地图偏移的反方向this.canvas.style.left = -this.offset_x + 'px';this.canvas.style.top = -this.offset_y + 'px';// 改变图片在canvas中的坐标// 图片坐标的变化方向,和地图相对于上次的偏移值‘方向相同this.changePicPosition(this.pixjsPic, this.offset_x_count, this.offset_y_count);
})

正常显示图片后,最后要做的就是在每次地图拖动结束后设置canvas的位置,同时也需要更新其中图片的位置,来保证1.canvas起点始终位于屏幕左上角 2.canvas中的图片始终与地图元素重合。

参考

arcgis 与 pixi.js 实现大数据量渲染 (一) - 简书

Leaflet中文文档

ArcGIS API实现气象数据可视化(esri公开课笔记)_跳墙网

ArcGIS API for Javascript 4.X扩展canvas在三维地图实现热力图

【基于Leaflet和Canvas绘图的前端大量栅格数据渲染】相关推荐

  1. leaflet地图原理_基于 leaflet 的标注绘图

    标注功能的背景 现在我们需要做一个标注工具,在一个视频上面绘制选框,把某个区域标记下来,接下来,把我们选框的内容坐标数据存储下来,这样作为一个分析的数据源,结合视频文件去分析选框区域内的内容情况. 结 ...

  2. h5学习笔记之canvas绘图(1)

    一.canvas标签的说明 在设置cancas大小的时候,必须在标签内写,如果在css内写会发生变化 默认宽度300px,默认高度 150px 背景颜色红色 的一个长方形 <canvas wid ...

  3. [转]基于Prototype,利用Canvas绘图实现的web流程图设计器(原型)

    基于Prototype,利用Canvas绘图实现的web流程图设计器(原型) 关键字: javascript prototype script.aculo.us canvas 流程图 web画线 刚才 ...

  4. 如何使用canvas绘图

    这里是修真院前端小课堂,每篇分享文从 [背景介绍][知识剖析][常见问题][解决方案][编码实战][扩展思考][更多讨论][参考文献] 八个方面深度解析前端知识/技能,本篇分享的是: [如何使用can ...

  5. H5 canvas 绘图

    H5的canvas绘图技术 canvas元素是HTML5中新添加的一个元素,该元素是HTML5中的一个亮点.Canvas元素就像一块画布,通过该元素自带的API结合JavaScript代码可以绘制各种 ...

  6. Canvas绘图在微信小程序中的应用:生成个性化海报

    Canvas绘图在微信小程序中的应用:生成个性化海报 如极客时间的一些实现案例: 基础语法 Canvas本质是一个可以使用脚本(通常为JavaScript)来绘制图形的 HTML 元素,默认大小为30 ...

  7. 前端实现可绘制的canvas画布_前端图形学基础(五)——Canvas状态管理

    点击右上角的关注,不定期前端干货分享!! 欢迎来到我的前端图形学系列文章: 前端图形学基础(一)--Canvas基础入门 前端图形学基础(二)--Canvas基础 前端图形学基础(三)--Canvas ...

  8. Canvas绘图基础(一)

    简单图形绘制 矩形:描边与填充 Canvas的API提供了三个方法,分别用于矩形的清除.描边及填充 clearRect(double x, double y, double w, double h) ...

  9. HTML5 学习总结(四)——canvas绘图、WebGL、SVG

    目录 一.Canvas 1.1.创建canvas元素 1.2.画线 1.3.绘制矩形 1.4.绘制圆弧 1.5.绘制图像 1.6.绘制文字 1.7.随机颜色与简单动画 二.WebGL 2.1.HTML ...

最新文章

  1. 【生活随想】实习结束以及开始校园招聘
  2. P1634 禽兽的传染病
  3. 蓝牙管理软件_Mac蓝牙检测软件----BlueSense
  4. 梯度方向直方图和物体检测Histogram of Oriented Gradients and Object Detection
  5. Stanford UFLDL教程 Softmax回归
  6. 【LeetCode】LC1672. 最富有客户的资产总量
  7. 【最详细】数据结构(C语言版 第2版)第八章课后习题答案 严蔚敏 等 编著
  8. 男生的哪个“不要”是真的不要?
  9. 放弃深度学习?我承认是因为线性代数
  10. 深度学习常用性能评价指标
  11. SVM支持向量和逻辑回归的decision_function用法详解
  12. 搭建Hadoop环境(超详细)
  13. Office之下拉字体选项名称为英文
  14. 英语十大词性之四 - 副词
  15. mysql for mac 重置密码_Mac版,mysql重置密码
  16. php htmlpurifier,htmlpurifierPHP过滤库
  17. android 中获取logcat缓冲区信息
  18. 运用京东云代码托管、云编译、云部署等产品进行蓝绿部署简单实践
  19. gerber 文件格式 [一]
  20. 禾川科技科创板上市破发:大跌17% 公司市值近30亿

热门文章

  1. qlikview连接数据库
  2. 阿里云注销备案流程及注销备案常见问题与解答
  3. RCC APB2PeriphClockCmd RCC APB1PeriphClockCmd
  4. linux下调试thread 类_linux下GDB调试
  5. 浪潮服务器上RAID配置实战及RAID相关知识
  6. C语言:模拟用户登录
  7. 乳腺癌(Breast cancer)数据集———Breakhis分享
  8. 自动驾驶在挑战中进化的感知能力
  9. java中的静态是什么?怎么用?
  10. 计算机与艺术联姻阶段,数字媒体艺术概论第二次作业