import { ILayer, LineLayer, PointLayer, PolygonLayer, Popup } from "@antv/l7"; import cloneDeep from "lodash/cloneDeep"; import HPaaSL7 from "../../HPaaSL7"; import ToolBase from "../base/ToolBase"; import { PolygonFeatureType } from "../data/PolygonStore"; import { EditEventMap } from "../../types"; import { bearing, bearingToAzimuth, distance, featureCollection, lineSegment, lineString, transformTranslate, lineToPolygon, coordAll, polygonToLine, } from "@turf/turf"; const isSamePoint = (point1: number[], point2: number[]) => { return point1[0] === point2[0] && point1[1] === point2[1]; }; const isSameLine = (line1Coords: number[][], line2Coords: number[][]) => { const [l1s, l1e] = line1Coords; const [l2s, l2e] = line2Coords; return (isSamePoint(l1s, l2s) && isSamePoint(l1e, l2e)) || (isSamePoint(l1e, l2s) && isSamePoint(l1s, l2e)); }; interface ControlPointInfo { lng: number; lat: number; fromLine: ControlLineInfo; } interface PointInfo { lng: number; lat: number; id: string; rawPoints: number[][]; // 用来标注高亮 active?: boolean; } interface ControlLineInfo { coordinates: number[][]; relatedPolygons: PolygonFeatureType[]; } interface DraggingPointInfo extends PointInfo { type: "point"; coordinates: number[]; } interface DraggingPolygonInfo { type: "polygon"; lat: number; lng: number; feature: PolygonFeatureType; } type DraggingFeatureInfo = DraggingPointInfo | DraggingPolygonInfo; class PolygonEditor extends ToolBase { private hpaas: HPaaSL7; private pointsLayer?: ILayer; private lineLayer?: ILayer; private bgLayer?: ILayer; private controlPointLayer?: ILayer; private popup?: Popup; private isEditing = false; private draggingFeatureInfo?: DraggingFeatureInfo; private pointsList: PointInfo[] = []; private controlLines: ControlLineInfo[] = []; editingFeatures: PolygonFeatureType[] = []; /** * 备份图形,用作撤销编辑还原用 */ private backup?: PolygonFeatureType[]; constructor(hpaas: HPaaSL7) { super(); this.hpaas = hpaas; } _bindEvent() { this.hpaas.l7Scene.on("mousemove", this._mousemoveEventHandler); this.hpaas.l7Scene.on("mouseup", this._mouseupEventHandler); this.pointsLayer?.on("mousedown", this._pointMousedownEventHandler); this.bgLayer?.on("mousedown", this._polygonMousedownEventHandler); this.lineLayer?.on("mousemove", this.showLineControlPoint); this.lineLayer?.on("mouseout", this.hideLineControlPoint); this.controlPointLayer?.on("mousedown", this.addControlPoint); this.pointsLayer?.on("click", this.toggleSelectPoint); document.addEventListener("keydown", this.removeActivePoint); } // TODO: 暂时先写在这里,后面移掉 removeActivePoint = (e: KeyboardEvent) => { const point = this.pointsList.find((point) => point.active); if (e.key === "Backspace" && point) { this.removePointFromPolygon(point); } }; _createLayer() { this.bgLayer = new PolygonLayer({ zIndex: 1100, }) .source(featureCollection([])) .color("#00AB3D") .shape("fill") .style({ opacity: 0.6, }); this.hpaas.l7Scene.addLayer(this.bgLayer); this.lineLayer = new LineLayer({ zIndex: 1100, }) .source([], { parser: { type: "json", coordinates: "coordinates", }, }) .size(1.5) .color("#00AB3D") .active({ color: "blue" }) .shape("line") .style({ opacity: 0.6, }); this.hpaas.l7Scene.addLayer(this.lineLayer); this.pointsLayer = new PointLayer({ zIndex: 1100, blend: "normal", pickingBuffer: 2, }) .source([], { parser: { type: "json", x: "lng", y: "lat", }, }) .shape("circle") .size(4); this.pointsLayer .color("active", (active) => { if (active) { return "blue"; } return "#00AB3D"; }) .active({ color: "#AE0F09", }) .style({ opacity: 1, stroke: "#fff", strokeWidth: 1, }); this.hpaas.l7Scene.addLayer(this.pointsLayer); this.controlPointLayer = new PointLayer({ zIndex: 1101, }) .source([], { parser: { type: "json", x: "lng", y: "lat", }, }) .shape("circle") .size(6) .color("#cd3de9") .active({ color: "#AE0F09", }) .style({ opacity: 1, stroke: "#fff", strokeWidth: 1, }); this.hpaas.l7Scene.addLayer(this.controlPointLayer); this.popup = new Popup({ // offsets: [iconSize / 2, 0], closeButton: false, stopPropagation: false, anchor: "bottom", }).setLnglat({ lng: 0, lat: 0 }); this._bindEvent(); } _pointMousedownEventHandler = (e: any) => { if (this.isEditing && e.feature) { this.draggingFeatureInfo = { type: "point", ...e.feature, }; this.hpaas.l7Scene.setMapStatus({ dragEnable: false, }); this.setActive(false); } }; _polygonMousedownEventHandler = (e: any) => { if (this.isEditing && e.feature) { const lng = e.lnglat ? e.lnglat.lng : e.lngLat.lng; const lat = e.lnglat ? e.lnglat.lat : e.lngLat.lat; const feature = e.feature; this.draggingFeatureInfo = { type: "polygon", lng, lat, feature, }; this.hpaas.l7Scene.setMapStatus({ dragEnable: false, }); this.setActive(false); } }; _mouseupEventHandler = (e: any) => { if (this.isEditing && e) { this.hpaas.l7Scene.setMapStatus({ dragEnable: true, }); this.draggingFeatureInfo = undefined; this.setActive(true); } }; _mousemoveEventHandler = (e: any) => { if (this.draggingFeatureInfo?.type === "point") { const pointInfo = this.draggingFeatureInfo as DraggingPointInfo; const lng = e.lnglat ? e.lnglat.lng : e.lngLat.lng; const lat = e.lnglat ? e.lnglat.lat : e.lngLat.lat; this.pointsList?.forEach((p) => { if (p.id == pointInfo.id) { p.lng = lng; p.lat = lat; p.rawPoints.forEach((point) => { point[0] = lng; point[1] = lat; }); } }); // TODO: 性能有问题 this.controlLines = this.generateControlLines(this.editingFeatures); this.emit("FEATURE_EDIT", this.editingFeatures); this.render(); } else if (this.draggingFeatureInfo?.type === "polygon") { const lng = e.lnglat ? e.lnglat.lng : e.lngLat.lng; const lat = e.lnglat ? e.lnglat.lat : e.lngLat.lat; const polygonInfo = this.draggingFeatureInfo as DraggingPolygonInfo; const _distance = distance([lng, lat], [polygonInfo.lng, polygonInfo.lat], { units: "meters", }); const _bearing = bearing([polygonInfo.lng, polygonInfo.lat], [lng, lat]); polygonInfo.feature = transformTranslate(polygonInfo.feature, _distance, bearingToAzimuth(_bearing), { units: "meters", }); polygonInfo.lng = lng; polygonInfo.lat = lat; this.emit("FEATURE_EDIT", [polygonInfo.feature]); const matchedFeature = this.editingFeatures.find( (feat) => feat.properties.id === polygonInfo.feature?.properties.id ); if (matchedFeature) { matchedFeature.geometry = polygonInfo.feature.geometry; } // TODO: 这里性能需要做优化,要不然拖动的时候会很卡 this.controlLines = this.generateControlLines(this.editingFeatures); this.pointsList = this.generatePointList(this.editingFeatures); this.render(); } else if (this.isEditing && e) { // const lng = e.lnglat ? e.lnglat.lng : e.lngLat.lng; // const lat = e.lnglat ? e.lnglat.lat : e.lngLat.lat; // this.popup?.setHTML(`
点击区域选择
`); // this.popup?.setLnglat([lng, lat]); // this.popup && this.hpaas.scene.addPopup(this.popup); // this.popup?.open(); } }; private showLineControlPoint = (e: any) => { if (this.draggingFeatureInfo) { return; } const [sPoint, ePoint] = e.feature.coordinates; const controlPoint: ControlPointInfo = { lng: (sPoint[0] + ePoint[0]) / 2, lat: (sPoint[1] + ePoint[1]) / 2, fromLine: e.feature, }; this.controlPointLayer?.setData([controlPoint]); }; private hideLineControlPoint = () => { this.controlPointLayer?.setData([]); }; private addControlPoint = (e: any) => { console.log(e); if (!e.feature) { return; } const controlPoint = e.feature as ControlPointInfo; controlPoint.fromLine.relatedPolygons.forEach((multiPolygon) => { multiPolygon.geometry.coordinates.forEach((polygon) => { polygon.forEach((_lineString) => { const _lineSegment = lineSegment(lineString(_lineString)); const sameLineIndex = _lineSegment.features.findIndex((line) => { return isSameLine(controlPoint.fromLine.coordinates, line.geometry.coordinates); }); if (sameLineIndex > -1) { _lineString.splice(sameLineIndex + 1, 0, [controlPoint.lng, controlPoint.lat]); } }); }); }); this.pointsList = this.generatePointList(this.editingFeatures); this.controlLines = this.generateControlLines(this.editingFeatures); this.hideLineControlPoint(); this.render(); }; private toggleSelectPoint = (e: any) => { this.pointsList.forEach((point) => { point.active = e.feature.id === point.id; }); this.render(); }; private removePointFromPolygon = (point: PointInfo) => { this.editingFeatures.forEach((multiPolygon) => { for (let i = 0; i < multiPolygon.geometry.coordinates.length; i++) { const polygon = multiPolygon.geometry.coordinates[i]; polygon.forEach((_lineString, index) => { for (let j = 0; j < _lineString.length; j++) { if (isSamePoint(_lineString[j], [point.lng, point.lat])) { _lineString.splice(j, 1); } } const points = lineToPolygon(lineString(_lineString)).geometry.coordinates[0] as number[][]; polygon[index] = points; }); } }); this.pointsList = this.generatePointList(this.editingFeatures); this.controlLines = this.generateControlLines(this.editingFeatures); this.render(); this.emit("FEATURE_EDIT", this.editingFeatures); }; /** * 是否允许 hover 高亮 */ setActive(status: boolean) { this.pointsLayer?.active(status); this.lineLayer?.active(status); if (!status) { this.hideLineControlPoint(); } } render() { this.pointsLayer?.setData(this.pointsList); this.lineLayer?.setData(this.controlLines); this.bgLayer?.setData(featureCollection(this.editingFeatures)); } /** * 生成所有的控制点 */ generatePointList(features: PolygonFeatureType[]) { const pointsList: PointInfo[] = []; features.forEach((multiPolygon) => { coordAll(multiPolygon).forEach((_p) => { const samePoint = pointsList.find((p) => p.lng === _p[0] && p.lat === _p[1]); if (samePoint) { samePoint.rawPoints.push(_p); } else { pointsList.push({ lng: _p[0], lat: _p[1], id: _p[0] + "-" + _p[1], rawPoints: [_p], }); } }); }); return pointsList; } /** * 生成所有的边线,以便吸附添加控制点 */ generateControlLines(features: PolygonFeatureType[]) { const controlLines: ControlLineInfo[] = []; features.forEach((_multiPolygon) => { const lines = lineSegment(polygonToLine(_multiPolygon)).features; lines.forEach((line) => { const sameLine = controlLines.find((controlLine) => { return isSameLine(controlLine.coordinates, line.geometry.coordinates); }); if (sameLine) { sameLine.relatedPolygons.push(_multiPolygon); } else { controlLines.push({ coordinates: line.geometry.coordinates, relatedPolygons: [_multiPolygon], }); } }); }); return controlLines; } begin(features: PolygonFeatureType[]) { this.backup = cloneDeep(features); this.editingFeatures = features; this.pointsList = this.generatePointList(features); this.controlLines = this.generateControlLines(features); this._createLayer(); this.isEditing = true; this.render(); } cancel() { const features = this.backup; this.clear(); return features; } clear() { this.isEditing = false; this.popup && this.popup.close(); /** * 清除数据 */ const clearData = () => { this.editingFeatures = []; this.backup = []; this.pointsList = []; this.controlLines = []; }; const removeLayer = () => { this.hpaas.l7Scene.removeLayer(this.bgLayer!); this.hpaas.l7Scene.removeLayer(this.lineLayer!); this.hpaas.l7Scene.removeLayer(this.pointsLayer!); this.hpaas.l7Scene.removeLayer(this.controlPointLayer!); }; const removeEvent = () => { this.hpaas.l7Scene.off("mousemove", this._mousemoveEventHandler); this.hpaas.l7Scene.off("mouseup", this._mouseupEventHandler); document.removeEventListener("keydown", this.removeActivePoint); }; clearData(); removeLayer(); removeEvent(); } } export default PolygonEditor;