> 文档中心 > Canvas绘制地图

Canvas绘制地图

在我们的大屏可视化项目中,地图数据可视化是最常见功能。地图数据可视化目前的实现方案有很多,其中最具有代表性的莫过于使用echarts,引入一个js文件,再加上一些简单的配置,这样一个地图就展示出来了。但是作为一名优秀的前端程序员,对于很多技术,我们既要知其然,也要只其所以然,本章节以HTML5中的Canvas技术为背景,简单讲解一下地图数据可视化实现的一些思路,希望能够给大家带来一些具有启发性的思考,具体实现效果如下图: 

地图数据可视化的实现步骤如下:

  1. 查询并下载地图数据文件,一般是geojson文件,世界地图、中国地图、行政单位地图均可
  2. 页面获取并解析geojson文件,为了让地图能充满指定区域,需要计算一下地图的包围盒范围并计算中心点经纬度,并且计算地图的缩放系数
  3. 遍历地图文件数据,将经纬度坐标转换为屏幕坐标,结合Canvas绘制多边形相关api,根据缩放系数将位置信息映射在画布上
  4. 响应鼠标事件处理,针对Canvas来说,其实就是重绘。这里主要用到了两个api——isPointInPath或isPointInStroke判断鼠标点击或悬浮区域是否在指定的区域上(难点)

参考资料:geojson地图经纬度坐标范围

接下来就开始进入正文——撸代码,页面布局结构如下:

 相关样式如下:

* {    margin: 0;    padding: 0;}html,body {    width: 100%;    height: 100%;}canvas {    display: block;    width: 100%;    height: 100%;}

 具体实现逻辑如下:

let canvas = document.querySelector('#container')let canvasW = canvas.width = window.innerWidthlet canvasH = canvas.height = window.innerHeightlet geoCenterX = 0, geoCenterY = 0  // 地图区域的经纬度中心点let scale = 1   // 地图缩放系数let geoData = []let offsetX = 0, offsetY = 0    // 鼠标事件的位置信息let eventType = ''  // 事件类型let ctx = canvas.getContext('2d')// 地图绘制入口方法function init() {    let request = new XMLHttpRequest()    request.open('get', 'https://geo.datav.aliyun.com/areas_v3/bound/410000_full.json')    //request.open('get', './henan.json')    request.send()    request.onload = function () { if (request.status === 200) {     geoData = JSON.parse(request.responseText)     getBoxArea()     drawMap() }    }}// 分三步,清空画布、绘制地图各子区域、标注城市名称function drawMap() {    ctx.clearRect(0, 0, canvasW, canvasH)    // 画布背景    ctx.fillStyle = '#000'    ctx.fillRect(0, 0, canvasW, canvasH)    drawArea()    drawText()}// 绘制地图各子区域function drawArea() {    let dataArr = geoData.features    let cursorFlag = false    for (let i = 0; i  {     ctx.save()     ctx.beginPath()     ctx.translate(centerX, centerY)     area[0].forEach((elem, index) => {  let position = toScreenPosition(elem[0], elem[1])  if (index === 0) {      ctx.moveTo(position.x, position.y)  } else {      ctx.lineTo(position.x, position.y)  }     })     ctx.closePath()     ctx.strokeStyle = '#00cccc'     ctx.lineWidth = 1     // 将鼠标悬浮的区域设置为橘黄色     if (ctx.isPointInPath(offsetX, offsetY)) {  cursorFlag = true  ctx.fillStyle = 'orange'  if (eventType === 'click') {      console.log(dataArr[i])  }     } else {  ctx.fillStyle = '#004444'     }     ctx.fill()     ctx.stroke()     ctx.restore() }); // 动态设置鼠标样式 if (cursorFlag) {     canvas.style.cursor = 'pointer' } else {     canvas.style.cursor = 'default' }    }}// 标注地图上的城市名称function drawText() {    let centerX = canvasW / 2    let centerY = canvasH / 2    geoData.features.forEach(item => { ctx.save() ctx.beginPath() ctx.translate(centerX, centerY) // 将画笔移至画布的中心 ctx.fillStyle = '#fff' ctx.font = '16px Microsoft YaHei' ctx.textAlign = 'center' ctx.textBaseLine = 'center' let x = 0, y = 0 //  因不同的geojson文件中中心点属性信息不同,这里需要做兼容性处理 if (item.properties.cp) {     x = item.properties.cp[0]     y = item.properties.cp[1] } else if (item.properties.centroid) {     x = item.properties.centroid[0]     y = item.properties.centroid[1] } else if (item.properties.center) {     x = item.properties.center[0]     y = item.properties.center[1] } let position = toScreenPosition(x, y) ctx.fillText(item.properties.name, position.x, position.y); ctx.restore()    })}// 将经纬度坐标转换为屏幕坐标function toScreenPosition(horizontal, vertical) {    return { x: (horizontal - geoCenterX) * scale, y: (geoCenterY - vertical) * scale    }}// 获取包围盒范围,计算包围盒中心经纬度坐标,计算地图缩放系数function getBoxArea() {    let N = -90, S = 90, W = 180, E = -180    geoData.features.forEach(item => { // 将MultiPolygon和Polygon格式的地图处理成统一数据格式 if (item.geometry.type === 'Polygon') {     item.geometry.coordinates = [item.geometry.coordinates] } // 取四个方向的极值 item.geometry.coordinates.forEach(area => {     let areaN = - 90, areaS = 90, areaW = 180, areaE = -180     area[0].forEach(elem => {  if (elem[0]  E) {      E = elem[0]  }  if (elem[1] > N) {      N = elem[1]  }  if (elem[1]  hScale ? hScale : wScale    // 获取包围盒中心经纬度坐标    geoCenterX = (E + W) / 2    geoCenterY = (N + S) / 2}// 滚轮缩放事件canvas.addEventListener('mousewheel', function (event) {    if (event.deltaY > 0) { if (scale > 10) {     scale -= 10 }    } else { scale += 10    }    eventType = 'mousewheel'    drawMap()})// 鼠标移动事件canvas.addEventListener('mousemove', function (event) {    offsetX = event.offsetX    offsetY = event.offsetY    eventType = 'mousemove'    drawMap()})// 鼠标点击事件canvas.addEventListener('click', function (event) {    offsetX = event.offsetX    offsetY = event.offsetY    eventType = 'click'    drawMap()})init()

看完的小伙伴,如果对你工作有帮助,记得点个赞,你的鼓励将是作者不断创作的动力,加油!!!