自定义图层
约 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' // 箭头鼠标
}
}