我魔改了高德多边型编辑器

背景

某天,业务突然找到正在摸鱼🐟的我,说想要一个工具,支持在高德地图上绘制矩形,并且可以编辑矩形。

我:(内心os:这玩意不是高德本来就支持吗?还要做?)老实的打开高德官方demo😏,说这些不是现成支持吗?高德矩形绘制和编辑

产品:我看过高德的矩形编辑了,不是我想要的,我要的是可以对4个点进行拖拽编辑,还要支持可以把矩形旋转任意角度,还要可以查看矩形的单边距离。

我:啊这...你说的这些高德好像都不支持,这些都要开发的😲

产品:我不管,大大大boss要的,我相信你一定可以搞出来的(撂下话,马上跑了)

我:...

目标

既然这么被信任(我瞟了一眼脖子上的亮光),那就老实的干吧;最后通过调研高德的一些能力另辟蹊径的实现了这个工具。

最终效果(支持编辑、旋转,测距在非编辑下支持)

技术方案

查看了高德的矩形绘制和编辑,发现能实现基础的矩形绘制和编辑,但是高德绘制的矩形,只能通过西南点和东北点绘制矩形,这样子就实现非垂直于x轴和y轴的矩形。考虑到这,想到多边形的能力比较齐全,即支持编辑也可以绘制任意方向的图形,脑子里突然多了一个想法,把这个魔改一下如何😆。

确认方案:

查看时

用多边形来绘制矩形。

编辑时

用多边形绘制的矩形,来作为数据源,进行编辑,在控制一下多边形的编辑行为,来达到矩形的编辑。

绘制矩形

通过多边形来绘制矩形:

方法一:四点创建矩形

比较简单,直接给多边形传递4个点的路径来创建矩形;

js 复制代码
const likeRectange = new AMap.Polygon({ path: [p1, p2, p3, p4] });

通过四点创建矩形,可以创建任意方向的矩形(因为本质还是创建多边形)。

方法二:两点创建矩形

通过 西南点经纬度(x₁, y₁) 和 东北点经纬度(x₂, y₂) 两点创建矩形;

通过计算,可以得到矩形的4个点:左上点:(x₂, y₁)、右上点:(x₂, y₂)、右下点:(x₂, y₁)、左下点:(x₁, y₁) ,然后在通过方法一创建矩形;

方法三:中心点+宽高创建矩形

设中心点经纬度(lng, lat),矩形宽(200) x 高(50)。

计算4个点坐标:

js 复制代码
const center = AMap.LngLat(lng, lat);
const widht = 200;
const height = 50;

const leftTopPoint = center.offset(-width/2, height/2); // 左上点
const rightTopPoint = center.offset(width/2, height/2); // 右上点
const rightBottomPoint = center.offset(width/2, -height/2); // 右下点
const leftBottomPoint = center.offset(-width/2, -height/2); // 左下点

得到4个点坐标,在通过方法一创建矩形。

矩形能力扩展

矩形的绘制功能已经支持了,接下来就是扩展矩形的一些能力了。

边长测距

实现思路:此时已经矩形的4个顶点的坐标,可以通过两两连线,得到4条直线,然后在地图上绘制4条不可见的线条(实际是通过高德的工具方法来判断),添加鼠标移入事件,触发两点距离计算。

js 复制代码
// 地图实例
const mapIns = new AMap.Map({ ... });

// 矩形实例
const likeRectangle = new AMap.Polygon({ path: [p1, p2, p3, p4] });

// 矩形4个点坐标
const paths = likeRectangle.getPath();

// 记录矩形4条直线的两个端点
let idx = 0;
const len = paths.length;
const linesPath = [];

while (idx < len) {
    const nextIdx = idx + 1 >= len ? 0 : idx + 1;
    linesPath.push([paths[idx], paths[nextIdx]]);
    idx++;
}

// 注册地图鼠标事件
mapIns.on('mousemove', (event) => {
    const { lnglat } = event;
    
    // 判断是否在两点的连线上
    const line = linesPath.find(path => {
        return AMap.GeometryUtil.isPointOnSegment(lnglat, path[0], path[1])
    });
    
    // 存在,即表示当前鼠标点在直线上,则显示提示文案
    if (line) {
        new AMap.Text({ text: 'x米', position: lnglat, map: mapIns });
    }
});

实现效果

矩形编辑

编辑能力因为是直接用多边形的矩形,故可以直接用多边形的编辑功能。但需要额外处理多边形编辑时的中间点,需要将这些中间点隐藏掉。

隐藏中间点

从多边形的编辑参数可以知道,中间点样式配置继承于 CircleMarkerOptions,可以把 radius 设置为 0 来达到隐藏的效果😆。

编辑点(白色点)行为控制

多边形编辑点默认行为:

  • 点击编辑点,会取消该编辑点
  • 编辑点支持的 adjust 事件 (ps: 这里有一个坑,adjust 只支持在编辑点移动完成后响应😬,即无法监控编辑点的开始、移动、结束行为)

上述两个默认行为比较致命,如果无法解决这两个问题,就不能得到一个可靠的矩形编辑功能,所以只能各个击破。

魔改

针对『点击编辑点,会取消该编辑点』,从编辑点参数可以知道,编辑点应该是通过 CircleMarker 来实现的,我们可以通过拿到编辑点实例,然后把默认的点击事件去移除。

js 复制代码
// 创建编辑器实例
const likeRectangeEditorIns = new LikeRectangleEditor(likeRectangle, { ... });

// 拿到编辑点(在实例找拿)
const editorPoints = [p1, p2, p3 ,p4];

// 移除默认的点击行为
editorPoints.forEach(point => point.clearEvents('click')) 

针对『编辑点支持的 adjust 事件』,一种思路类似通过修改 CircleMarker 的事件行为来实现;另一种思路则可以通过地图点击事件 + 是否点击到编辑点上等组合逻辑(实际逻辑比较复杂),在实现开始、移动、结束事件的监听。

具体实现可以去查看源码。

相邻两点位置实时计算

这里就要用到初中知识了(忘记的同学还可以补习一波~)

简单点的描述就是求平面坐标内的两条互相垂直的交点(特性:两条互相垂直的直线,斜率乘积 = -1)。

矩形旋转

首先,高德地图并不支持矢量图形旋转的功能,所以这块需要自己实现。

这里可以拆解成两步来实现:

1、实现旋转能力;

2、根据旋转角度,计算出4个顶点新的经纬度。

旋转能力

这里直接用了一个三方库 moveable 来实现了,不过多介绍。

大致思路是通过 moveable 控制旋转,然后实时更新旋转后的点位。

旋转后的点位的经纬度计算

js 复制代码
// 矩形的中心点,也是矩形的旋转点
const centerPoint = new AMap.LngLat(cLng, cLat);

// 将经纬度转为平面坐标
const centerPointPixel = mapIns.lngLatToContainer(centerPoint);

// 左上点
const leftTopPoint = new AMap.LngLat(lng, lat);

// 将经纬度转为平面坐标
const leftTopPointPixel = mapIns.lngLatToContainer(leftTopPoint);

// 求点P(x1,y1)绕另一个点O(x2,y2)旋转θ度的坐标
// 计算公式:
// x = (x1 - x2) * cos(θ * Math.PI / 180) - (y1 - y2) * sin(θ * Math.PI / 180) + x2
// y = (x1 - x2) * sin(θ * Math.PI / 180) - (y1 - y2) * cos(θ * Math.PI / 180) + y2
const calcRotatePoint = (point: AMap.Pixel, center: AMap.Pixel, angle: number) => {
    const x1 = point.x;
    const y1 = point.y;
    const x2 = center.x;
    const y2 = center.y;
    const angleRad = (angle * Math.PI) / 180;

    const dx = x1 - x2;
    const dy = y1 - y2;
    const newX = dx * Math.cos(angleRad) - dy * Math.sin(angleRad) + x2;
    const newY = dx * Math.sin(angleRad) + dy * Math.cos(angleRad) + y2;
    return { x: newX, y: newY };
}

// 然后将新的平面坐标转位经纬度坐标
const nextLeftTopPointPixel = calcRotatePoint(leftTopPointPixel, center, 30);
// 左上点旋转 θ 度后的坐标
const nextLeftTopPoint = mapIns.containerToLngLat(new AMap.Pixel(nextLeftTopPointPixel.x, nextLeftTopPointPixel.y));

// 通过同理计算其他点位

矩形拖动

多边形本身支持了拖动能力,我们需要特殊处理的是在拖动的时候,同步更新矩形的中心点、四个顶点位置以及旋转点的坐标。

js 复制代码
likeRectangle.on('dragend', () => {
    // 拖动完成后,更新矩形
    ...
    
    // 更新矩形中心点
    ...
    
    // 更新
    ...
});å

后续优化

动态测距: 是指矩形在编辑点操作时,可以实时计算临接的两个动点之间的实时距离。

边界控制: 目前高德的矩形可能,有一个最小的绘制控制,以及当前操作点不可移动到相邻两点外。

指定旋转中心: 目前只支持绕矩形的中心点进行旋转,后续支持可以指定旋转中心进行旋转。

总结

相关推荐
谁呛我名字2 小时前
大数据应用开发——数据可视化
javascript·vue.js·echarts
前端郭德纲2 小时前
浅谈React的虚拟DOM
前端·javascript·react.js
2401_879103683 小时前
24.11.10 css
前端·css
ComPDFKit4 小时前
使用 PDF API 合并 PDF 文件
前端·javascript·macos
yqcoder4 小时前
react 中 memo 模块作用
前端·javascript·react.js
谈谈叭4 小时前
Javascript中的深浅拷贝以及实现方法
开发语言·javascript·ecmascript
优雅永不过时·5 小时前
Three.js 原生 实现 react-three-fiber drei 的 磨砂反射的效果
前端·javascript·react.js·webgl·threejs·three
爱编程的鱼5 小时前
javascript用来干嘛的?赋予网站灵魂的语言
开发语言·javascript·ecmascript
神夜大侠7 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱7 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js