自定义图层

约 757 字大约 3 分钟

自定义图层,主要是通过创建 canvas 画布,在 canvas 中处理各种数据展示与交互,然后追加到地图容器适当位置下,通常都是追加 esri-view-root 节点内。

<div id="js_map" style="height: 100vh"></div>
import Map from "@arcgis/core/Map"
import MapView from "@arcgis/core/views/MapView"
import { useVehicleStore } from '@/stores/vehicle'

// 导入svg文件
import vehicleBlue from '@/assets/svg/vehicle-blue.svg'
import vehicleGreen from '@/assets/svg/vehicle-green.svg'

// svg图片预加载
const vehicleImg: any = {}
const preloadImg = (svgUrls: string[]) => {
  svgUrls.forEach((url: string) => {
    const key: string = url!.split('-')!.pop()!.split('.')[0]
    vehicleImg[key] = new Image()
    vehicleImg[key].src = url!
  })
}
preloadImg([
  vehicleBlue,
  vehicleGreen
])

let ctx: CanvasRenderingContext2D | null = null

const map: any = new Map({
  basemap: 'dark-gray',
})

const mapView: any = new MapView({
  map,
  container: 'js_map',
  center: [102.92934063304513, 25.102234987110343],
  zoom: 3,
})

mapView.when(() => {
  drawVehicleLayer()
})

// 监听浏览器窗口变化
window.addEventListener("resize", () => {
  drawVehicleLayer()
})

// 监听范围变化
mapView.watch("extent", () => {
  drawVehicleMarker(ctx, mapView)
})

// 点击事件
mapView.on('click', (event: any) => {
  drawVehicleMarker(ctx, mapView, {
    x: event.x,
    y: event.y
  })
})

// 鼠标移动触发(处理手形鼠标)
mapView.on('pointer-move', (event: any) => {
  drawVehicleMarker(ctx, mapView, {
    x: event.x,
    y: event.y,
    event: 'mouse'
  })
})

// 绘制车辆层
const drawVehicleLayer = () => {
  const mapEle = document.getElementById('js_map') as HTMLDivElement
  const vehicleLayerEle = document.getElementById('js_vehicle_layer')
  if(vehicleLayerEle) {
    vehicleLayerEle.querySelector('canvas').width = mapEle.offsetWidth;
    vehicleLayerEle.querySelector('canvas').height = mapEle.offsetHeight;
  } else {
    // 创建div
    const div = document.createElement('div')
    div.id = 'js_vehicle_layer'
    div.style.cssText = "pointer-events: none"
    div.className = 'esri-view-surface'
    // 创建canvas
    const canvas = document.createElement('canvas')
    canvas.width = mapEle.offsetWidth
    canvas.height = mapEle.offsetHeight
    ctx = canvas.getContext('2d')  
    // 追加节点
    div.appendChild(canvas)
    const rootEle = mapEle.querySelector('.esri-view-root') as HTMLDivElement
    rootEle.appendChild(div)
  }
  // 绘制车辆标记
  drawVehicleMarker(ctx, mapView)
}

// 经纬度坐标 转 屏幕坐标
const toScreenCoord = (coord: number[]) => {
  if (coord[0] && coord[1]) {
    const point = {
      x: coord[0],
      y: coord[1],
      spatialReference: {
        wkid: 4326, // WGS84坐标系
      }
    }
    return mapView.toScreen(point)
  }
}

// 枚举zoom缩放比例
const enumZoomScale = (zoom: number): number => {
  switch (zoom) {
    case 14:
      return 0.6;
    case 15:
      return 0.8;
    case 16:
      return 1;
    case 17:
      return 1.2;
    case 18:
      return 1.4;
    case 19:
      return 1.6;
    case 20:
      return 1.8;
    case 21:
      return 2;
    case 22:
      return 2.2;
    case 23:
      return 2.4;
    default:
      return 1;
  }
};

// 绘制车辆
const drawVehicleMarker = (ctx: any, mapView: any, opts: any={}) => {
  if(!ctx) return
  ctx.clearRect(0 , 0, window.innerWidth, window.innerHeight-64) // 清空画布
  if (mapView.zoom <= 13) return;
  const vehicleStore = useVehicleStore()
  const {
    vehicleData: [{number: '测试车123', x: 132.321, y: 22.33213, vehicletype: 'adsifj23'}],
    vehicleTypeData = {json: {'adsifj23': '测试车', 'wersf123': '工作车'}},
    filter = {vehicle: {show: true, types: ['adsifj23'], display: 'vnum'}}
  } = vehicleStore
  const rootView = document.querySelector('.esri-view-root') as HTMLDivElement
  const totals = { // 用于鼠标悬浮判断
    all: 0,
    out: 0
  }
  vehicleData.forEach((item: any) => {
    if(item.x || item.y) {
      totals.all++
      const screenCoord = toScreenCoord([item.x, item.y])
      const zoomScale = enumZoomScale(mapView.zoom)
      ctx.save()
      ctx.translate(screenCoord.x, screenCoord.y) // 平移
      ctx.rotate(2*Math.PI*((item.direct)/360)) // 旋转
      ctx.scale(zoomScale, zoomScale) // 缩放
      ctx.translate(-screenCoord.x, -screenCoord.y)
      if(filter.vehicle.show && (filter.vehicle.types as string[]).includes(item.vehicletype)) {
        // 点击区
        ctx.beginPath()
        ctx.arc(screenCoord.x, screenCoord.y, 14, 0, Math.PI * 2)
        ctx.fillStyle = 'transparent'
        ctx.fill()
        if(opts.event === 'mouse') { // 鼠标悬浮
          if(item.selected) {
            ctx.drawImage(vehicleImg['green'], screenCoord.x-14, screenCoord.y-14, 28, 28)
          } else {
            ctx.drawImage(vehicleImg['blue'], screenCoord.x-14, screenCoord.y-14, 28, 28)
          }
          if(rootView) {
            if(opts.x && ctx.isPointInPath(opts.x, opts.y)) { // 判断是否在点击区内
              rootView.style.cursor = 'pointer' // 手形鼠标
            } else {
              totals.out++
            }
          }
        } else {
          if(item.selected) { // 点击其他区域,去除选中状态
            item.selected = false
            vehicleStore.setVehicleData(vehicleData)
          }
          if((opts.x && ctx.isPointInPath(opts.x, opts.y)) || item.selected) { // 判断是否在点击区内 或 是否已选中
            if(!item.selected) {
              item.selected = true
              vehicleStore.setVehicleData(vehicleData)
            }
            ctx.drawImage(vehicleImg['green'], screenCoord.x-14, screenCoord.y-14, 28, 28)
          } else {
            ctx.drawImage(vehicleImg['blue'], screenCoord.x-14, screenCoord.y-14, 28, 28)
          }
        }
      }
      ctx.restore()
      ctx.save()
      // 标签绘制
      if(
        filter.vehicle.show && 
        (filter.vehicle.types as string[]).includes(item.vehicletype) && 
        filter.vehicle.display !== 'hidden'
      ) {
        ctx.shadowColor = 'rgba(0, 0, 0, 1)'
        ctx.shadowBlur = 5
        ctx.font = "12px Arial"
        ctx.fillStyle = "#fff"
        if(filter.vehicle.display === 'vnum') {
          ctx.fillText(item.number, screenCoord.x + 28, screenCoord.y - 2)
        } else {
          ctx.fillText(vehicleTypeData.json[item.vehicletype], screenCoord.x + 28, screenCoord.y - 2)
        }
      }
      ctx.restore()
    }
  })
  if(totals.all === totals.out) {
    rootView.style.cursor = 'default' // 箭头鼠标
  }
}
上次编辑于: