在开始绘制polygon之前,我们需要先搭建好开发环境。我推荐使用HBuilderX作为开发工具,它内置了uniapp项目模板,能快速创建微信小程序项目。安装完成后,新建一个uniapp项目,记得在manifest.json中勾选微信小程序平台支持。
这里有个容易踩坑的地方:微信小程序地图组件需要申请权限。你需要在微信公众平台的小程序后台,找到"开发"-"开发管理"-"接口设置",申请"wx.getLocation"和"wx.chooseLocation"接口权限。我遇到过因为忘记申请权限导致地图无法显示的问题,排查了半天才发现是这个原因。
基础地图配置很简单,在pages/index/index.vue中添加map组件:
html复制<map
id="myMap"
style="width: 100%; height: 300px;"
:latitude="latitude"
:longitude="longitude"
:scale="scale"
@tap="handleTap"
></map>
对应的data配置建议这样写:
javascript复制data() {
return {
latitude: 39.90469, // 默认北京中心点
longitude: 116.40717,
scale: 16,
markers: [],
polygons: [],
isDrawing: false // 标记是否处于绘制状态
}
}
地图初始化后,我们需要处理绘制状态的切换。我建议使用一个明显的按钮来控制绘制状态的开启/关闭,这样用户体验更好。在map组件旁边添加一个操作按钮:
html复制<button @click="toggleDrawing">{{ isDrawing ? '结束绘制' : '开始绘制' }}</button>
对应的toggleDrawing方法需要处理状态切换和用户提示:
javascript复制methods: {
toggleDrawing() {
this.isDrawing = !this.isDrawing
uni.showToast({
title: this.isDrawing ? '请点击地图添加顶点' : '已结束绘制',
icon: 'none'
})
if (!this.isDrawing && this.tempPoints.length > 2) {
this.createPolygon()
}
}
}
这里我引入了一个tempPoints数组来临时存储绘制过程中的顶点坐标。这个设计是为了避免直接修改polygons数据,等绘制完成后再一次性提交。实际开发中我发现,频繁操作polygons数组会导致渲染性能下降,特别是在移动设备上。
接下来是实现核心的绘制功能。我们需要监听地图的tap事件来采集顶点坐标:
javascript复制handleTap(e) {
if (!this.isDrawing) return
const { latitude, longitude } = e.detail
this.tempPoints.push({ latitude, longitude })
// 实时更新polyline显示
this.updatePolyline()
// 显示当前顶点标记
this.markers.push({
id: Date.now(),
latitude,
longitude,
iconPath: '/static/vertex.png',
width: 20,
height: 20
})
}
updatePolyline方法负责更新实时连线:
javascript复制updatePolyline() {
this.polyline = [{
points: this.tempPoints,
color: '#3388FF',
width: 3,
dottedLine: false
}]
}
这里有个重要的细节处理:微信小程序的map组件要求polyline的points数组必须包含至少两个点。我遇到过因为忘记做空值判断导致小程序崩溃的情况。所以实际代码中应该加上:
javascript复制if (this.tempPoints.length < 2) return
当用户完成顶点采集后,我们需要将采集的点转换为多边形。微信小程序没有原生的双击事件,所以我采用了时间差判断法来模拟双击事件:
javascript复制handleTap(e) {
// ...之前代码
// 双击判断
const now = Date.now()
if (this.lastTapTime && now - this.lastTapTime < 300) {
this.handleDoubleTap()
}
this.lastTapTime = now
}
handleDoubleTap() {
if (this.tempPoints.length < 3) {
uni.showToast({
title: '至少需要3个点才能形成面',
icon: 'none'
})
return
}
this.createPolygon()
this.toggleDrawing() // 自动结束绘制状态
}
createPolygon方法将临时点转换为多边形:
javascript复制createPolygon() {
this.polygons.push({
points: this.tempPoints,
strokeWidth: 2,
strokeColor: '#3388FF',
fillColor: '#3388FF33'
})
this.tempPoints = []
this.polyline = []
}
这里有个性能优化点:微信小程序的map组件在polygons数据变化时会重新渲染整个地图。如果有多边形编辑需求,建议使用一个中间变量来存储正在编辑的多边形,等编辑完成后再合并到polygons数组中。
在实际开发中,我遇到了几个典型问题,这里分享解决方案:
问题1:地图缩放干扰绘制
双击地图会触发缩放操作,与我们的双击结束绘制冲突。解决方案是在绘制状态下禁用缩放:
html复制<map :scale="scale" :enable-zoom="!isDrawing"></map>
问题2:顶点误操作
用户可能不小心点错位置。建议添加顶点删除功能,长按标记点可删除:
javascript复制handleMarkerLongPress(e) {
const { markerId } = e.detail
const index = this.markers.findIndex(m => m.id === markerId)
if (index >= 0) {
this.markers.splice(index, 1)
this.tempPoints.splice(index, 1)
this.updatePolyline()
}
}
问题3:多边形交叉验证
复杂的多边形需要验证是否自相交。虽然微信小程序没有内置验证,但我们可以用射线法实现:
javascript复制isPolygonValid(points) {
// 简化版射线法实现
for (let i = 0; i < points.length; i++) {
for (let j = i + 1; j < points.length; j++) {
if (this.checkIntersection(points[i], points[(i+1)%points.length],
points[j], points[(j+1)%points.length])) {
return false
}
}
}
return true
}
基础功能完成后,可以考虑添加一些增强用户体验的功能:
撤销上一步操作
javascript复制undoLastPoint() {
if (this.tempPoints.length > 0) {
this.tempPoints.pop()
this.markers.pop()
this.updatePolyline()
}
}
多边形编辑模式
javascript复制enterEditMode(index) {
this.editingIndex = index
this.tempPoints = [...this.polygons[index].points]
this.isDrawing = true
// 显示所有顶点标记
this.markers = this.tempPoints.map((p, i) => ({
id: i,
latitude: p.latitude,
longitude: p.longitude,
iconPath: '/static/vertex.png'
}))
}
面积计算
使用球面几何公式计算多边形面积:
javascript复制calculateArea(points) {
let area = 0
const R = 6378137 // 地球半径(米)
// 实现球面多边形面积计算算法
// ...
return Math.abs(area / 2)
}
当多边形较复杂时,需要注意性能优化:
javascript复制handleTap: _.throttle(function(e) {
// 实际处理代码
}, 300)
html复制<polygon :points="polygon.points" static></polygon>
javascript复制const mapCtx = wx.createMapContext('myMap')
mapCtx.addGroundOverlay({
id: 'polygon',
points: this.tempPoints,
color: '#3388FF33'
})