需求说明
如图所示,中间区域为第三方调用的区域卫星地图,仅显示指定区域,区域外其余图片信息为空;设计人员为了提升视觉效果,增加了区域边界和区域外的一张暗色底图,需要在地图调整缩放级别时,暗色底图也能同步缩放,且保持相对定位。
找了一下arcgis的官方示例,有个自定义遮罩层的功能比较接近最终效果,实际上是使用了BaseLayerView2D可以创造扩展子类的特性实现了自定义图层,学会了这个方法后其实我们还可以创造更多可观的自定义图层。
developers.arcgis.com/javascript/…
实现思路
自定义图层还是切片地图的模式,即又多块指定大小的正方形切片拼接而成,每个切片的内容可以用cavas绘制,实现步骤如下:
- 加载底图文件,完成后开始绘制图层切片;
-
假设当前缩放程度为level, 每个切片在当前level下会有固定的所在行row和所在列col属性(row,col可为负数),作为其唯一标识;
-
确定当前level下,底图左上角所在的行列,并以此为原点绘制所有切片,比如切片大小为512,原点位于第0行第0列,则切片col0_row0选取底图从(0,0)到(512,512)的裁剪图作为画布内容;
-
计算行政区域边界线与当前切片的交集A,绘制线段A;
-
找到边界线和当前切片的面交集M,并挖空M,让底下的卫星图显示出来。
-
调整缩放程度level时,重新执行步骤2
-
调整边界线数据时,重新执行步骤2
相关代码
主要框架
内部方法执行顺序为constructor -> attach -> render
// 声明自定义图层视图
const CustomLayerView2D = BaseLayerView2D.createSubclass({
//构造器
constructor: function(){
},
// 图层添加到map实例后执行
attach: async function () {
// 加载背景图片
// 监听各种属性变化后重绘
this.watchHandles.add([])
},
// 图层被销毁时执行
detach: () => {
if (this && this.watchHandles) {
//清除监听
this.watchHandles.removeAll()
}
},
// 渲染器
render(){
// 更新每个切片,在视野中的切片则重绘,不在视野中的销毁
this.manageTileImages()
// 如果整个地图旋转角度不为0的话,将所有切片旋转
...
},
// 图层与鼠标行为的碰撞检测
hitTest(mapPoint, { x, y }){
return promiseUtils.resolve({
//自定义碰撞事件
})
}
})
// 声明自定义图层类
const MaskLayerClass = Layer.createSubclass({
properties: {
color: {}, //边界线颜色
geometry: {}, // 非遮罩区域多边形 Polygon
id: '', // 图层id
title: '', // 图层标题
visible: false, // 图层可见
spatialReference: { wkid: 4326 }, // 坐标系WGS84
background: '', // 底图路径
tileConf: {
9: { //地图缩放级别level
minCol: -1, //原点所在列
minRow: -3, //原点所在行
offsetX: -740, //底图x偏移值
offsetY: -200, //底图y偏移值
tileSize: 1500, //每个切片包含底图的尺寸
border: 6 //边界线宽度
},
...
} // 切片配置
},
constructor: function (conf) {
this.color = [0, 0, 0, 1]
// 切片信息
// 坐标系spatialReference一定要声明!!!
this.tileInfo = TileInfo.create({
size: SIZE,
spatialReference: conf.spatialReference
})
},
// 创建图层视图
createLayerView: function (view) {
if (view.type === '2d') {
return new CustomLayerView2D({
view,
layer: this
})
}
}
})
// 实例化图层
const layer = new MaskLayerClass({
...
})
绘制单个切片内容
// 绘制单个切片
drawGeometry: function (ctx, bounds, tileIndex, tileInfo) {
ctx.globalCompositeOperation = 'source-over'
const width = ctx.canvas.width
const height = ctx.canvas.height
// No geometry; entire map is unmasked.
if (!this.projectedGeometry) {
ctx.clearRect(0, 0, width, height)
return
}
const { col, row, level } = tileInfo
const tileMap = this.layer.tileConf
// 切片贴图
if (tileMap[level]) {
const { minCol, minRow, offsetX, offsetY, tileSize } = tileMap[level]
// 偏移法绘制瓦片
const startX = (col - minCol) * tileSize + offsetX
const startY = (row - minRow) * tileSize + offsetY
ctx.drawImage(
this.backgroundImg,
startX, // 开始裁剪x坐标
startY, // 开始裁剪y坐标
tileSize, tileSize, // 裁剪尺寸
0, 0, // 图像开始位置
SIZE, SIZE // 最终图像尺寸
)
}
绘制单个切片边界
// 获整个边界数据
const rings =
this.projectedGeometry.type === 'extent'
? Polygon.fromExtent(this.projectedGeometry).rings
: this.projectedGeometry.rings ||
this.projectedGeometry.paths
// 边界坐标转为切片内坐标
const transformed = rings.map((ring) => {
return ring.map((coords) => {
return [
Math.round(
(width * (coords[0] - bounds[0])) /
(bounds[2] - bounds[0])
),
Math.round(
height *
(1 - (coords[1] - bounds[1]) / (bounds[3] - bounds[1]))
)
]
})
})
ctx.lineJoin = 'round'
const [red, green, blue, alpha] = this.layer.color
// 绘制边缘
ctx.strokeStyle = `rgba(${red},${green},${blue},${alpha})`
ctx.lineWidth = tileMap[level] ? tileMap[level].border: 20
for (let i = 0; i < transformed.length; ++i) {
let ring = transformed[i]
ctx.beginPath()
ctx.moveTo(ring[0][0], ring[0][1])
for (let j = 1; j < ring.length; ++j) {
ctx.lineTo(ring[j][0], ring[j][1])
}
this.projectedGeometry.type !== 'polyline' && ctx.closePath()
ctx.stroke()
}
镂空单个切片的边界内区域
// 只有源图像外的目标图像部分会被显示,源图像是透明的
ctx.globalCompositeOperation = 'destination-out'
ctx.fillStyle = 'rgba(0, 0, 0, 1)'
for (let i = 0; i < transformed.length; ++i) {
const ring = transformed[i]
ctx.beginPath()
ctx.moveTo(ring[0][0], ring[0][1])
for (let j = 1; j < ring.length; ++j) {
ctx.lineTo(ring[j][0], ring[j][1])
}
ctx.closePath()
ctx.fill()
}
扩展思考
2D模式下的arcgis自定义图层都可以在 BaseLayerView2D.createSubclass 的基础上使用Canvas或webGL 技术,查看更多官方效果可以在以下链接搜索 “BaseLayerView2D”
developers.arcgis.com/javascript/…
3D模式可以使用 externalRenderers 实现自定义图层