383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
import React, { useState, useEffect, useCallback, useRef } from "react";
|
|
import ToolbarButton from "@components/olmap/common/ToolbarButton";
|
|
|
|
// 导入Material-UI图标
|
|
import BackHandOutlinedIcon from "@mui/icons-material/BackHandOutlined";
|
|
import BorderColorOutlinedIcon from "@mui/icons-material/BorderColorOutlined";
|
|
import MoreHorizOutlinedIcon from "@mui/icons-material/MoreHorizOutlined";
|
|
import TimelineIcon from "@mui/icons-material/Timeline";
|
|
import CircleOutlinedIcon from "@mui/icons-material/CircleOutlined";
|
|
import CheckBoxOutlineBlankIcon from "@mui/icons-material/CheckBoxOutlineBlank";
|
|
import GestureIcon from "@mui/icons-material/Gesture";
|
|
import UndoIcon from "@mui/icons-material/Undo";
|
|
import RedoIcon from "@mui/icons-material/Redo";
|
|
import DeleteIcon from "@mui/icons-material/Delete";
|
|
import SaveIcon from "@mui/icons-material/Save";
|
|
|
|
// 导入OpenLayers绘图相关模块
|
|
import Draw, {
|
|
DrawEvent,
|
|
createBox,
|
|
GeometryFunction,
|
|
} from "ol/interaction/Draw";
|
|
import VectorSource from "ol/source/Vector";
|
|
import VectorLayer from "ol/layer/Vector";
|
|
import { Style, Stroke, Fill, Circle } from "ol/style";
|
|
import { Geometry } from "ol/geom";
|
|
import Feature from "ol/Feature";
|
|
import { Type as GeometryType } from "ol/geom/Geometry";
|
|
import { useMap } from "../MapComponent";
|
|
|
|
const DrawPanel: React.FC = () => {
|
|
const map = useMap();
|
|
const [activeTool, setActiveTool] = useState<string>("pan");
|
|
const [drawLayer, setDrawLayer] = useState<VectorLayer<VectorSource> | null>(
|
|
null
|
|
);
|
|
const [drawnFeatures, setDrawnFeatures] = useState<Feature<Geometry>[]>([]);
|
|
const [historyStack, setHistoryStack] = useState<Feature<Geometry>[][]>([]);
|
|
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
|
|
|
const drawInteractionRef = useRef<Draw | null>(null);
|
|
|
|
// 创建并添加绘图图层
|
|
useEffect(() => {
|
|
if (!map) return;
|
|
const drawSource = new VectorSource();
|
|
const drawVectorLayer = new VectorLayer({
|
|
source: drawSource,
|
|
style: new Style({
|
|
stroke: new Stroke({
|
|
color: `rgba(255, 152, 0, 0.9)`,
|
|
width: 2,
|
|
}),
|
|
fill: new Fill({
|
|
color: `rgba(255, 152, 0, 0.3)`,
|
|
}),
|
|
image: new Circle({
|
|
radius: 7,
|
|
stroke: new Stroke({
|
|
color: `rgba(255, 152, 0, 0.9)`,
|
|
width: 2,
|
|
}),
|
|
fill: new Fill({
|
|
color: `rgba(255, 152, 0, 0.3)`,
|
|
}),
|
|
}),
|
|
}),
|
|
});
|
|
|
|
map.addLayer(drawVectorLayer);
|
|
setDrawLayer(drawVectorLayer);
|
|
|
|
return () => {
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
map.removeLayer(drawVectorLayer);
|
|
};
|
|
}, [map, drawInteractionRef]);
|
|
|
|
// 保存到历史记录
|
|
const saveToHistory = useCallback(
|
|
(features: Feature<Geometry>[]) => {
|
|
setHistoryStack((prevStack) => {
|
|
const newHistory = prevStack.slice(0, historyIndex + 1);
|
|
newHistory.push([...features]);
|
|
setHistoryIndex(newHistory.length - 1);
|
|
return newHistory;
|
|
});
|
|
},
|
|
[historyIndex]
|
|
);
|
|
|
|
// 添加绘图交互
|
|
const addDrawInteraction = (
|
|
type: GeometryType,
|
|
geometryFunction?: GeometryFunction
|
|
) => {
|
|
if (!drawLayer) return;
|
|
if (!map) return;
|
|
|
|
// 清除现有的绘图交互
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
}
|
|
|
|
const source = drawLayer.getSource();
|
|
if (!source) return;
|
|
|
|
const drawOptions: {
|
|
source: VectorSource;
|
|
type: GeometryType;
|
|
style: Style;
|
|
geometryFunction?: GeometryFunction;
|
|
} = {
|
|
source: source,
|
|
type: type,
|
|
style: new Style({
|
|
stroke: new Stroke({
|
|
color: `rgba(255, 152, 0, 0.9)`,
|
|
width: 2,
|
|
}),
|
|
fill: new Fill({
|
|
color: `rgba(255, 152, 0, 0.3)`,
|
|
}),
|
|
image: new Circle({
|
|
radius: 7,
|
|
stroke: new Stroke({
|
|
color: `rgba(255, 152, 0, 0.9)`,
|
|
width: 2,
|
|
}),
|
|
fill: new Fill({
|
|
color: `rgba(255, 152, 0, 0.3)`,
|
|
}),
|
|
}),
|
|
}),
|
|
};
|
|
|
|
// 如果有几何函数,添加它
|
|
if (geometryFunction) {
|
|
drawOptions.geometryFunction = geometryFunction;
|
|
}
|
|
|
|
const draw = new Draw(drawOptions);
|
|
|
|
// 绘图完成事件
|
|
draw.on("drawend", (event: DrawEvent) => {
|
|
const feature = event.feature;
|
|
const currentFeatures = [...drawnFeatures, feature];
|
|
setDrawnFeatures(currentFeatures);
|
|
saveToHistory(currentFeatures);
|
|
});
|
|
|
|
map.addInteraction(draw);
|
|
drawInteractionRef.current = draw;
|
|
};
|
|
|
|
// 处理工具点击
|
|
const handleToolClick = (tool: string) => {
|
|
// 如果点击的是当前激活的工具,则取消激活
|
|
// console.log("当前激活的工具:", activeTool);
|
|
// console.log("点击的工具:", tool);
|
|
if (activeTool === tool) {
|
|
setActiveTool("");
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
return;
|
|
}
|
|
if (
|
|
tool !== "undo" &&
|
|
tool !== "redo" &&
|
|
tool !== "delete" &&
|
|
tool !== "save"
|
|
) {
|
|
setActiveTool(tool);
|
|
}
|
|
// 根据工具类型处理不同的交互
|
|
switch (tool) {
|
|
case "pan":
|
|
// 平移地图,移除所有绘图交互
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
break;
|
|
case "select":
|
|
// 选定要素,移除所有绘图交互
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
break;
|
|
case "edit":
|
|
// 编辑要素,移除所有绘图交互
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
break;
|
|
case "point":
|
|
addDrawInteraction("Point");
|
|
break;
|
|
case "line":
|
|
addDrawInteraction("LineString");
|
|
break;
|
|
case "circle":
|
|
addDrawInteraction("Circle");
|
|
break;
|
|
case "box":
|
|
// 使用矩形绘制函数
|
|
addDrawInteraction("Circle", createBox());
|
|
break;
|
|
case "polygon":
|
|
addDrawInteraction("Polygon");
|
|
break;
|
|
case "undo":
|
|
handleUndo();
|
|
break;
|
|
case "redo":
|
|
handleRedo();
|
|
break;
|
|
case "delete":
|
|
handleDelete();
|
|
break;
|
|
case "save":
|
|
handleSave();
|
|
break;
|
|
default:
|
|
if (drawInteractionRef.current && map) {
|
|
map.removeInteraction(drawInteractionRef.current);
|
|
drawInteractionRef.current = null;
|
|
}
|
|
}
|
|
};
|
|
|
|
// 撤销功能
|
|
const handleUndo = () => {
|
|
if (historyIndex > 0) {
|
|
const newIndex = historyIndex - 1;
|
|
const previousFeatures = historyStack[newIndex];
|
|
updateDrawLayer(previousFeatures);
|
|
setDrawnFeatures(previousFeatures);
|
|
setHistoryIndex(newIndex);
|
|
}
|
|
};
|
|
|
|
// 重做功能
|
|
const handleRedo = () => {
|
|
if (historyIndex < historyStack.length - 1) {
|
|
const newIndex = historyIndex + 1;
|
|
const nextFeatures = historyStack[newIndex];
|
|
updateDrawLayer(nextFeatures);
|
|
setDrawnFeatures(nextFeatures);
|
|
setHistoryIndex(newIndex);
|
|
}
|
|
};
|
|
|
|
// 删除所有绘制的要素
|
|
const handleDelete = () => {
|
|
if (!drawLayer) return;
|
|
|
|
const source = drawLayer.getSource();
|
|
if (source) {
|
|
source.clear();
|
|
const emptyFeatures: Feature<Geometry>[] = [];
|
|
setDrawnFeatures(emptyFeatures);
|
|
saveToHistory(emptyFeatures);
|
|
}
|
|
};
|
|
|
|
// 保存绘制的要素
|
|
const handleSave = () => {};
|
|
|
|
// 更新绘图图层
|
|
const updateDrawLayer = (features: Feature<Geometry>[]) => {
|
|
if (!drawLayer) return;
|
|
|
|
const source = drawLayer.getSource();
|
|
if (source) {
|
|
source.clear();
|
|
source.addFeatures(features);
|
|
}
|
|
};
|
|
|
|
// 初始化历史记录
|
|
useEffect(() => {
|
|
// 初始化空的历史记录
|
|
if (historyStack.length === 0) {
|
|
saveToHistory([]);
|
|
}
|
|
}, [historyStack.length, saveToHistory]);
|
|
|
|
// 判断按钮是否应该禁用
|
|
const isUndoDisabled = historyIndex <= 0;
|
|
const isRedoDisabled = historyIndex >= historyStack.length - 1;
|
|
const isDeleteDisabled = drawnFeatures.length === 0;
|
|
const isSaveDisabled = drawnFeatures.length === 0;
|
|
|
|
return (
|
|
<div className="absolute top-20 left-4 bg-white p-1 rounded-xl shadow-lg flex flex-col opacity-85 hover:opacity-100 transition-opacity z-10">
|
|
<div className="flex">
|
|
<ToolbarButton
|
|
icon={<BackHandOutlinedIcon />}
|
|
name="平移地图"
|
|
isActive={activeTool === "pan"}
|
|
onClick={() => handleToolClick("pan")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<BorderColorOutlinedIcon />}
|
|
name="矢量编辑"
|
|
isActive={activeTool === "edit"}
|
|
onClick={() => handleToolClick("edit")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<MoreHorizOutlinedIcon />}
|
|
name="绘制点"
|
|
isActive={activeTool === "point"}
|
|
onClick={() => handleToolClick("point")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<TimelineIcon />}
|
|
name="绘制线"
|
|
isActive={activeTool === "line"}
|
|
onClick={() => handleToolClick("line")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<CircleOutlinedIcon />}
|
|
name="绘制圆"
|
|
isActive={activeTool === "circle"}
|
|
onClick={() => handleToolClick("circle")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<CheckBoxOutlineBlankIcon />}
|
|
name="绘制框"
|
|
isActive={activeTool === "box"}
|
|
onClick={() => handleToolClick("box")}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<GestureIcon />}
|
|
name="绘制多边形"
|
|
isActive={activeTool === "polygon"}
|
|
onClick={() => handleToolClick("polygon")}
|
|
/>
|
|
</div>
|
|
<div className="flex mt-1 border-t-1 pt-1">
|
|
<ToolbarButton
|
|
icon={<UndoIcon />}
|
|
name="撤销"
|
|
isActive={false}
|
|
onClick={() => handleToolClick("undo")}
|
|
disabled={isUndoDisabled}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<RedoIcon />}
|
|
name="重做"
|
|
isActive={false}
|
|
onClick={() => handleToolClick("redo")}
|
|
disabled={isRedoDisabled}
|
|
/>
|
|
<ToolbarButton
|
|
icon={<DeleteIcon />}
|
|
name="删除"
|
|
isActive={false}
|
|
onClick={() => handleToolClick("delete")}
|
|
disabled={isDeleteDisabled}
|
|
/>
|
|
{/* <ToolbarButton
|
|
icon={<SaveIcon />}
|
|
name="保存"
|
|
isActive={false}
|
|
onClick={() => handleToolClick("save")}
|
|
disabled={isSaveDisabled}
|
|
/> */}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DrawPanel;
|