Canvas绘制地图
在我们的大屏可视化项目中,地图数据可视化是最常见功能。地图数据可视化目前的实现方案有很多,其中最具有代表性的莫过于使用echarts,引入一个js文件,再加上一些简单的配置,这样一个地图就展示出来了。但是作为一名优秀的前端程序员,对于很多技术,我们既要知其然,也要只其所以然,本章节以HTML5中的Canvas技术为背景,简单讲解一下地图数据可视化实现的一些思路。
·
在我们的大屏可视化项目中,地图数据可视化是最常见功能。地图数据可视化目前的实现方案有很多,其中最具有代表性的莫过于使用echarts,引入一个js文件,再加上一些简单的配置,这样一个地图就展示出来了。但是作为一名优秀的前端程序员,对于很多技术,我们既要知其然,也要只其所以然,本章节以HTML5中的Canvas技术为背景,简单讲解一下地图数据可视化实现的一些思路,希望能够给大家带来一些具有启发性的思考,具体实现效果如下图:
地图数据可视化的实现步骤如下:
- 查询并下载地图数据文件,一般是geojson文件,世界地图、中国地图、行政单位地图均可
- 页面获取并解析geojson文件,为了让地图能充满指定区域,需要计算一下地图的包围盒范围并计算中心点经纬度,并且计算地图的缩放系数
- 遍历地图文件数据,将经纬度坐标转换为屏幕坐标,结合Canvas绘制多边形相关api,根据缩放系数将位置信息映射在画布上
- 响应鼠标事件处理,针对Canvas来说,其实就是重绘。这里主要用到了两个api——isPointInPath或isPointInStroke判断鼠标点击或悬浮区域是否在指定的区域上(难点)
参考资料:geojson地图经纬度坐标范围
接下来就开始进入正文——撸代码,页面布局结构如下:
<canvas id="container"></canvas>
相关样式如下:
* {
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.innerWidth
let canvasH = canvas.height = window.innerHeight
let 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 < dataArr.length; i++) {
let centerX = canvasW / 2
let centerY = canvasH / 2
dataArr[i].geometry.coordinates.forEach(area => {
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] < W) {
W = elem[0]
}
if (elem[0] > E) {
E = elem[0]
}
if (elem[1] > N) {
N = elem[1]
}
if (elem[1] < S) {
S = elem[1]
}
})
})
})
// 计算包围盒的宽高
let width = Math.abs(E - W)
let height = Math.abs(N - S)
let wScale = canvasW / width
let hScale = canvasH / height
// 计算地图缩放系数
scale = wScale > 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()
看完的小伙伴,如果对你工作有帮助,记得点个赞,你的鼓励将是作者不断创作的动力,加油!!!
更多推荐
已为社区贡献2条内容
所有评论(0)