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

背景

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

我:(内心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', () => {
    // 拖动完成后,更新矩形
    ...
    
    // 更新矩形中心点
    ...
    
    // 更新
    ...
});å

后续优化

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

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

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

总结

相关推荐
卸任7 小时前
Vibe coding工具使用心得
前端·ai编程
brien98 小时前
为什么模型标称 1M,Google Antigravity 聊到 256K 却突然失忆?我们给它做了个原生行内监控圆环
前端
修炼的dance8 小时前
录屏关键帧间隔多少合适?六档实测,2秒到10秒小了75.6%
前端
Lank_M8 小时前
关键帧从1秒拉到10秒,录屏小87.3%,摄像头小10.8%
前端
补码缩写8 小时前
用 ffprobe 拆录屏,八成字节在 29 个关键帧上
前端
百思不得小李8 小时前
一个复杂的动态表单是如何实现的
前端·设计模式
陈随易9 小时前
Cmder 停更 17 个月,2.0 支持 Windows Terminal
前端·后端·程序员
清晨Coding9 小时前
删了 Cookie 还能自动登录?Google系网站登录态自动恢复的原理分析
javascript
风骏时光牛马9 小时前
前沿视界
前端
kyriewen9 小时前
我扫了 814 个依赖,97 个还敢要 Node 10
前端·javascript·程序员