前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >基于canvas和ol的点标注的避让实现

基于canvas和ol的点标注的避让实现

作者头像
lzugis
发布2021-12-21 14:13:00
5040
发布2021-12-21 14:13:00
举报

概述

在做地图的时候,点的标注展示是一个非常常见的功能,但是十几种点在某些区域比较密集是非常常见的,但是业务表达中却需要将之展示出来。基于此需求,本文结合canvas和ol做一简单的实现。

效果

image.png
image.png
image.png
image.png

实现:

  1. 密集区点的标注通过牵引线的方式引出展示;
  2. 地图放大的时候更新展示;

思路

image.png
image.png

实现代码

const points = [
  { "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 110.15558, 19.91038 ] } },
  { "properties": {"name":"测试名称应"}, "geometry": { "type": "Point", "coordinates": [ 113.52309, 22.21177 ] } },
  { "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 114.23454, 22.21177 ] } },
  { "properties": {"name":"测该很长"}, "geometry": { "type": "Point", "coordinates": [ 119.35695, 26.06293 ] } },
  { "properties": {"name":"测试名称应该称应该"}, "geometry": { "type": "Point", "coordinates": [ 120.11582, 30.07927 ] } },
  { "properties": {"name":"测试名称应"}, "geometry": { "type": "Point", "coordinates": [ 121.49129, 31.14058 ] } },
  { "properties": {"name":"测试名称应该"}, "geometry": { "type": "Point", "coordinates": [ 117.03289, 23.5228 ] } }
]
let canvasFunction = function (extent, resolution, pixelRatio, size, projection) {
  const canvasWidth = size[0]
  const canvasHeight = size[1]
  const [w, h] = map.getSize()
  const xoff = canvasWidth - w,
    yoff = canvasHeight - h
  const canvas = document.createElement('canvas');
  canvas.width = canvasWidth
  canvas.height = canvasHeight
  const context = canvas.getContext('2d');
  // 数据聚类处理,根据上下和左右的距离进行判断
  function clusterData(data) {
    let res = {}
    let clusterTest = function (pixel, tolrance = [200, 30]) {
      let r = pixel.join(',')
      const [x, y] = pixel
      for (let key in res) {
        const [_x, _y] = key.split(',').map(Number)
        const dx = Math.abs(x - _x),
          dy = Math.abs(y - _y)
        if(dx < tolrance[0] && dy < tolrance[1]) {
          r = key
          break
        }
      }
      return r
    }
    for (let i = 0; i < data.length; i++) {
      const d = data[i]
      const coords = ol.proj.fromLonLat(d.geometry.coordinates)
      let pixel = map.getPixelFromCoordinate(coords)
      pixel = [pixel[0]  + xoff / 2, pixel[1] + yoff / 2].map(p => Math.round(p))
      d.pixel = pixel
      let key = pixel.join(',')
      const width = 26 * 2 + 6 + context.measureText(d.properties.name).width
      key = clusterTest(pixel, [width, 30])
      if(!res[key]) res[key] = []
      res[key].push(d)
    }
    return res
  }
  // 绘制两边为圆的矩形
  function drawRoundRect (ctx, x, y, width, height = 24, fillStyle = 'rgba(14,77,137,0.75)') {
    const r = height / 2
    ctx.fillStyle = fillStyle
    ctx.beginPath()
    ctx.moveTo(x + r, y)
    ctx.lineTo(x + width - r, y)
    ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 0.5)
    ctx.lineTo(x + r, y + height)
    ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI * 1.5)
    ctx.closePath()
    ctx.fill()
  }
  // 绘制feature
  function drawFeature (ctx, x = 10, y = 10, text, notCluster = true, index = 0) {
    let height = 26, width = height * 2 + 6,  r = height / 2
    if(notCluster) width += ctx.measureText(text).width
    ctx.save()
    // 如果有聚类,则避让绘制文字,放在前面是为了让指引线在下面
    if(!notCluster && map.getView().getZoom() > 4) {
      const radius = 60
      const ang  = (-index * 40 - 115) / 180 * Math.PI ;
      const cx = x + r, cy = y + height - r
      const px = cx + Math.sin(ang) * radius,
        py = cy + Math.cos(ang) * radius
      // 绘制牵引线
      ctx.beginPath()
      ctx.strokeStyle = 'rgba(14,77,137,0.75)'
      ctx.lineWidth = 2
      ctx.moveTo(cx, cy)
      ctx.lineTo(px, py)
      ctx.stroke()
      // 绘制牵引线终点小圆圈
      ctx.beginPath()
      ctx.fillStyle = 'rgba(14,77,137,1)'
      ctx.arc(px, py, 2, 0, Math.PI * 2)
      ctx.fill()
      // 绘制矩形
      const h = 18
      const w = ctx.measureText(text).width + 12
      drawRoundRect(ctx, px - w - 2, py - h / 2 - 1, w, h, 'rgba(14,77,137,0.5)')
      // 绘制文字
      ctx.fillStyle = 'rgb(255,255,255)'
      ctx.beginPath()
      ctx.textAlign = 'right'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, px - 7, py)
    }
    // 绘制矩形
    drawRoundRect(ctx, x, y, width, height)
    // 绘制左边的图标
    ctx.beginPath()
    const radialLeft = ctx.createRadialGradient(x + r, y + height - r, 0, x + r, y + height - r, r)
    radialLeft.addColorStop(0, '#fff')
    radialLeft.addColorStop(1, 'rgba(255,255,255,0)')
    ctx.fillStyle = radialLeft
    ctx.arc(x + r, y + height - r, r, 0, Math.PI * 2)
    ctx.fill()
    // 绘制右边的图标
    ctx.beginPath()
    const radialRight = ctx.createRadialGradient(x + width - r, y + r, 0, x + width - r, y + r, r)
    radialRight.addColorStop(0, '#fff')
    radialRight.addColorStop(1, 'rgba(255,255,255,0)')
    ctx.fillStyle = radialRight
    ctx.arc(x + width - r, y + r, r, 0, Math.PI * 2)
    ctx.fill()
    // 如果没有聚类,则绘制文字
    if(notCluster) {
      ctx.fillStyle = '#fff'
      ctx.beginPath()
      ctx.textAlign = 'left'
      ctx.textBaseline = 'middle'
      ctx.fillText(text, x + height + 3, y + height - r)
    }
    ctx.restore()
  }
  const cluster = clusterData(points)
  for (let key in cluster) {
    const data = cluster[key]
    const showText = data.length === 1
    data.forEach((d, index) => {
      const [x, y] = d.pixel
      drawFeature(context, x, y, d.properties.name, showText, index)
    })
  }
  return canvas;
}
const layer = new ol.layer.Image({
  source: new ol.source.ImageCanvas({
    canvasFunction: canvasFunction
  })
});
map.addLayer(layer);
script>
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-12-17 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 概述
  • 效果
  • 实现:
  • 思路
  • 实现代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档