微信小程序里做电子围栏功能,核心并不只是把一块区域画在地图上,而是要让系统知道用户是否处在划定范围内,并能对特定区域做视觉强调。原生map组件提供了polygons属性用来渲染多边形,但点击判断和区域高亮需要开发者自己组合API实现。下面从绘制、高亮到命中检测逐步拆解。

多边形坐标采集与map组件渲染
要实现自定义围栏,第一步是在地图上让用户或代码确定多个顶点。通常我们会监听map的bindtap事件,在回调中拿到经纬度e.detail.longitude与e.detail.latitude,推入一个数组。当点数达到三个及以上,就能构成一个闭合多边形。需要注意的是,微信小程序map的polygons数据格式要求points为{latitude, longitude}对象数组,且顶点顺序应为顺时针或逆时针一致,否则在某些基础库版本会出现填充异常。
将采集到的点绑定到data中的polygons字段,通过setData驱动地图重绘。我们还可以为多边形配置strokeWidth、fillColor、strokeColor等参数,让边界和填充更符合业务视觉。如果涉及多个独立围栏,polygons应为数组嵌套,每一个元素描述一个区域。下面的代码展示了如何初始化并追加一个点:
Page({
data: {
polygons: [],
points: []
},
onMapTap(e) {
const pt = { latitude: e.detail.latitude, longitude: e.detail.longitude };
const points = this.data.points.concat(pt);
this.setData({ points });
if (points.length >= 3) {
this.setData({
polygons: [{
points: points,
strokeWidth: 2,
strokeColor: '#FF0000AA',
fillColor: '#FF000033'
}]
});
}
}
});
这种直接利用map组件渲染的方式性能很好,因为绘制工作由原生层完成,不会阻塞逻辑线程。但它的局限在于无法做复杂的动态高亮,比如鼠标悬停变色,也难以在多边形上绘制文字标签。因此在需要强交互时,我们往往要结合canvas覆盖层。
使用canvas叠加层实现区域高亮
map组件本身是原生组件,层级高于普通view,所以不能直接用view做高亮遮罩。可行方案是在map上方覆盖一个同尺寸的canvas,并把地图视野转换为canvas像素坐标,手动绘制多边形路径并填充半透明色。高亮的本质是:监听手势移动,计算当前点归属哪个多边形,然后在canvas上重绘该多边形为更醒目的颜色。
坐标转换依赖map的longitude、latitude与视野缩放级别。我们可以通过mapContext.getRegion得到当前可视经纬度范围,再按canvas宽高做线性映射。下面示例展示在canvas上绘制高亮多边形的核心逻辑,其中toPixel是自定义的经纬度到像素转换函数:
function drawHighlight(ctx, points, region, width, height) {
ctx.beginPath();
points.forEach((p, i) => {
const px = ((p.longitude - region.southwest.longitude) /
(region.northeast.longitude - region.southwest.longitude)) * width;
const py = ((region.northeast.latitude - p.latitude) /
(region.northeast.latitude - region.southwest.latitude)) * height;
if (i === 0) ctx.moveTo(px, py);
else ctx.lineTo(px, py);
});
ctx.closePath();
ctx.fillStyle = 'rgba(255, 120, 0, 0.4)';
ctx.fill();
}
canvas叠加层方案的优点是自由度高,不仅能高亮,还能画渐变、动画与标注。缺点是每次地图平移缩放都要重算坐标并重绘,逻辑复杂度上升。如果仅做静态围栏展示,建议仍用map的polygons;若做运营后台式强交互,canvas更合适。实践中也有团队用cover-view配合图片模拟高亮,但灵活度远不如canvas。
射线法实现点击区域命中判断
当用户点击地图某点,我们需要判断该点是否在多边形内部,这就是点击判断。最常用的数学模型是射线法(也称奇偶规则):从目标点向右发射一条水平射线,统计它与多边形各边相交的次数,奇数表示在内部,偶数表示在外部。该算法对凸多边形和凹多边形都有效,时间复杂度为O(n)。
在小程序里,我们可以在map的bindtap中获取点击经纬度,然后遍历polygons的每个顶点调用射线法。要注意浮点精度问题,建议给边界加极小容差。以下代码实现了判断函数,并在命中时触发业务逻辑:
function pointInPolygon(pt, points) {
let inside = false;
for (let i = 0, j = points.length - 1; i < points.length; j = i++) {
const xi = points[i].longitude, yi = points[i].latitude;
const xj = points[j].longitude, yj = points[j].latitude;
const intersect = ((yi > pt.latitude) !== (yj > pt.latitude)) &&
(pt.longitude < (xj - xi) * (pt.latitude - yi) / (yj - yi) + xi);
if (intersect) inside = !inside;
}
return inside;
}
Page({
onMapTap(e) {
const tap = { longitude: e.detail.longitude, latitude: e.detail.latitude };
const hit = this.data.polygons.some(p => pointInPolygon(tap, p.points));
if (hit) {
wx.showToast({ title: '已进入围栏区域' });
} else {
wx.showToast({ title: '在围栏外' });
}
}
});
对于多个不相交围栏,逐个判断即可。若存在嵌套围栏(如小区内含楼栋),单纯奇偶规则可能把内圈误判为外部,此时应记录射线穿过的总边数并按业务定义内外层级。此外,大量多边形时可将区域做网格索引,减少每次点击的遍历量。总体而言,微信小程序自定义map多边形绘制结合射线法,能以较轻量方式交付电子围栏、区域高亮与点击判断的完整能力。