|
@@ -0,0 +1,849 @@
|
|
|
|
|
+import config from "@/api/config.js";
|
|
|
|
|
+import * as KMap from "@/utils/ol-map/KMap";
|
|
|
|
|
+import * as util from "@/common/ol_common.js";
|
|
|
|
|
+import Point from "ol/geom/Point.js";
|
|
|
|
|
+import Feature from "ol/Feature";
|
|
|
|
|
+import VectorLayer from "ol/layer/Vector.js";
|
|
|
|
|
+import VectorSource from "ol/source/Vector";
|
|
|
|
|
+import WKT from "ol/format/WKT.js";
|
|
|
|
|
+import ScaleLine from "ol/control/ScaleLine";
|
|
|
|
|
+import { useRouter } from "vue-router";
|
|
|
|
|
+import locationIcon from "@/assets/images/map/location.png";
|
|
|
|
|
+import OLPolygon from "ol/geom/Polygon.js";
|
|
|
|
|
+import * as proj from "ol/proj";
|
|
|
|
|
+import Fill from "ol/style/Fill";
|
|
|
|
|
+import Stroke from "ol/style/Stroke";
|
|
|
|
|
+import Style from "ol/style/Style";
|
|
|
|
|
+import Icon from "ol/style/Icon";
|
|
|
|
|
+import Circle from "ol/style/Circle";
|
|
|
|
|
+import Text from "ol/style/Text";
|
|
|
|
|
+import Modify from "ol/interaction/Modify";
|
|
|
|
|
+import Translate from "ol/interaction/Translate";
|
|
|
|
|
+import { getArea } from "ol/sphere";
|
|
|
|
|
+
|
|
|
|
|
+/**
|
|
|
|
|
+ * @description 地图层对象
|
|
|
|
|
+ */
|
|
|
|
|
+class IndexMap {
|
|
|
|
|
+ constructor() {
|
|
|
|
|
+ let that = this;
|
|
|
|
|
+ let vectorStyle = new KMap.VectorStyle();
|
|
|
|
|
+ this.vectorStyle = vectorStyle;
|
|
|
|
|
+
|
|
|
|
|
+ // 位置图标
|
|
|
|
|
+ this.clickPointLayer = new KMap.VectorLayer("clickPointLayer", 9999, {
|
|
|
|
|
+ style: (f) => {
|
|
|
|
|
+ return new Style({
|
|
|
|
|
+ image: new Icon({
|
|
|
|
|
+ src: locationIcon,
|
|
|
|
|
+ scale: 0.4,
|
|
|
|
|
+ anchor: [0.5, 1],
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 地块图层:默认不显示编辑点
|
|
|
|
|
+ this.areaLayer = new KMap.VectorLayer("areaLayer", 100, {
|
|
|
|
|
+ minZoom: 2,
|
|
|
|
|
+ style: (f) => {
|
|
|
|
|
+ const styles = [];
|
|
|
|
|
+ const baseStyle = new Style({
|
|
|
|
|
+ fill: new Fill({
|
|
|
|
|
+ color: "rgba(59, 45, 2, 0.31)",
|
|
|
|
|
+ }),
|
|
|
|
|
+ stroke: new Stroke({
|
|
|
|
|
+ color: "#FFD489",
|
|
|
|
|
+ width: 2,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ styles.push(baseStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算并显示面积(亩数)
|
|
|
|
|
+ try {
|
|
|
|
|
+ const geometry = f.getGeometry();
|
|
|
|
|
+ const geom = geometry.clone();
|
|
|
|
|
+ // 转换坐标系用于计算面积
|
|
|
|
|
+ geom.transform(proj.get("EPSG:4326"), proj.get("EPSG:3857"));
|
|
|
|
|
+ let area = getArea(geom);
|
|
|
|
|
+ // 转换为亩:1平方米 = 0.0015亩,但根据代码中的公式是 (area + area / 2) / 1000
|
|
|
|
|
+ area = (area + area / 2) / 1000;
|
|
|
|
|
+ const areaText = area.toFixed(2) + "亩";
|
|
|
|
|
+
|
|
|
|
|
+ // 添加面积文字标签
|
|
|
|
|
+ const areaTextStyle = new Style({
|
|
|
|
|
+ text: new Text({
|
|
|
|
|
+ text: areaText,
|
|
|
|
|
+ font: "26px PangMenZhengDao",
|
|
|
|
|
+ fill: new Fill({
|
|
|
|
|
+ color: "#FFFFFF",
|
|
|
|
|
+ }),
|
|
|
|
|
+ stroke: new Stroke({
|
|
|
|
|
+ color: "#000",
|
|
|
|
|
+ width: 2,
|
|
|
|
|
+ }),
|
|
|
|
|
+ textAlign: "center",
|
|
|
|
|
+ textBaseline: "middle",
|
|
|
|
|
+ overflow: true,
|
|
|
|
|
+ placement: "point",
|
|
|
|
|
+ offsetY: 20,
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ styles.push(areaTextStyle);
|
|
|
|
|
+
|
|
|
|
|
+ // 计算多边形的中心点并添加中心点图标
|
|
|
|
|
+ try {
|
|
|
|
|
+ const extent = geometry.getExtent();
|
|
|
|
|
+ const centerX = (extent[0] + extent[2]) / 2;
|
|
|
|
|
+ const centerY = (extent[1] + extent[3]) / 2;
|
|
|
|
|
+ const centerCoord = [centerX, centerY];
|
|
|
|
|
+
|
|
|
|
|
+ const centerPointStyle = new Style({
|
|
|
|
|
+ geometry: new Point(centerCoord),
|
|
|
|
|
+ image: new Icon({
|
|
|
|
|
+ src: locationIcon,
|
|
|
|
|
+ scale: 0.4,
|
|
|
|
|
+ anchor: [0.5, 1],
|
|
|
|
|
+ }),
|
|
|
|
|
+ });
|
|
|
|
|
+ styles.push(centerPointStyle);
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("计算中心点失败:", error);
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("计算面积失败:", error);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果处于编辑模式,显示编辑点
|
|
|
|
|
+ if (f.get("icon") === "point-act") {
|
|
|
|
|
+ const geometry = f.getGeometry();
|
|
|
|
|
+ const type = geometry.getType();
|
|
|
|
|
+ let rings = [];
|
|
|
|
|
+ if (type === "Polygon") {
|
|
|
|
|
+ rings = geometry.getCoordinates();
|
|
|
|
|
+ } else if (type === "MultiPolygon") {
|
|
|
|
|
+ const polys = geometry.getCoordinates();
|
|
|
|
|
+ polys.forEach((poly) => {
|
|
|
|
|
+ rings = rings.concat(poly);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ rings.forEach((ring, ringIndex) => {
|
|
|
|
|
+ if (!ring || ring.length < 2) return;
|
|
|
|
|
+ for (let i = 0; i < ring.length - 1; i++) {
|
|
|
|
|
+ // 检查是否是选中的点
|
|
|
|
|
+ const isSelected =
|
|
|
|
|
+ this.selectedPointFeature === f &&
|
|
|
|
|
+ this.selectedRingIndex === ringIndex &&
|
|
|
|
|
+ this.selectedPointIndex === i;
|
|
|
|
|
+
|
|
|
|
|
+ styles.push(
|
|
|
|
|
+ new Style({
|
|
|
|
|
+ geometry: new Point(ring[i]),
|
|
|
|
|
+ image: new Circle({
|
|
|
|
|
+ radius: 6,
|
|
|
|
|
+ fill: new Fill({
|
|
|
|
|
+ // 选中的点显示为红色
|
|
|
|
|
+ color: isSelected ? "#FF0000" : i % 2 ? "#FFD489" : "#FFFFFF",
|
|
|
|
|
+ }),
|
|
|
|
|
+ stroke: new Stroke({
|
|
|
|
|
+ color: isSelected ? "#FF0000" : "#FFD489",
|
|
|
|
|
+ width: isSelected ? 2 : 1,
|
|
|
|
|
+ }),
|
|
|
|
|
+ }),
|
|
|
|
|
+ zIndex: 101,
|
|
|
|
|
+ })
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+ return styles;
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 编辑相关状态
|
|
|
|
|
+ this.isEditMode = false;
|
|
|
|
|
+ this.editModify = null;
|
|
|
|
|
+ this.originalGeometries = null;
|
|
|
|
|
+ this.editHistory = [];
|
|
|
|
|
+ this.editHistoryIndex = -1;
|
|
|
|
|
+ this.hasInitialState = false;
|
|
|
|
|
+ this.isUndoing = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 点选择相关状态
|
|
|
|
|
+ this.selectedPointFeature = null;
|
|
|
|
|
+ this.selectedPointIndex = -1;
|
|
|
|
|
+ this.selectedRingIndex = 0;
|
|
|
|
|
+ this.pointClickHandler = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 拖拽交互(用于非编辑模式下拖拽多边形)
|
|
|
|
|
+ this.translateInteraction = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ initMap(location, target) {
|
|
|
|
|
+ let level = 16;
|
|
|
|
|
+ let coordinate = util.wktCastGeom(location).getFirstCoordinate();
|
|
|
|
|
+ this.kmap = new KMap.Map(target, level, coordinate[0], coordinate[1], null, 6, 22);
|
|
|
|
|
+ let xyz2 = config.base_img_url3 + "map/lby/{z}/{x}/{y}.png";
|
|
|
|
|
+ this.kmap.addXYZLayer(xyz2, { minZoom: 8, maxZoom: 22 }, 2);
|
|
|
|
|
+ // this.kmap.addLayer(this.clickPointLayer.layer);
|
|
|
|
|
+ this.kmap.addLayer(this.areaLayer.layer);
|
|
|
|
|
+ // this.setMapPoint(coordinate);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setMapPoint(coordinate) {
|
|
|
|
|
+ this.clickPointLayer.source.clear();
|
|
|
|
|
+ let point = new Feature(new Point(coordinate));
|
|
|
|
|
+ this.clickPointLayer.addFeature(point);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ clearLayer() {
|
|
|
|
|
+ // 清除图层时,移除拖拽交互
|
|
|
|
|
+ this.disableTranslate();
|
|
|
|
|
+
|
|
|
|
|
+ if (this.areaLayer && this.areaLayer.layer) {
|
|
|
|
|
+ this.areaLayer.layer.getSource().clear();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setAreaGeometry(geometryArr) {
|
|
|
|
|
+ this.clearLayer();
|
|
|
|
|
+ let that = this;
|
|
|
|
|
+ const source = new VectorSource();
|
|
|
|
|
+
|
|
|
|
|
+ geometryArr.forEach((item) => {
|
|
|
|
|
+ const format = new WKT();
|
|
|
|
|
+ const mapProjection = that.kmap.map.getView().getProjection();
|
|
|
|
|
+ let geometry = format.readGeometry(item, {
|
|
|
|
|
+ dataProjection: "EPSG:4326",
|
|
|
|
|
+ featureProjection: mapProjection,
|
|
|
|
|
+ });
|
|
|
|
|
+ let f = new Feature({ geometry: geometry });
|
|
|
|
|
+ source.addFeature(f);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ this.areaLayer.layer.setSource(source);
|
|
|
|
|
+ this.fitView();
|
|
|
|
|
+
|
|
|
|
|
+ // 启用拖拽交互(非编辑模式下可以拖拽多边形)
|
|
|
|
|
+ this.enableTranslate();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 启用拖拽交互
|
|
|
|
|
+ enableTranslate() {
|
|
|
|
|
+ if (!this.areaLayer || !this.areaLayer.layer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source || source.getFeatures().length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果已经存在拖拽交互,先移除
|
|
|
|
|
+ if (this.translateInteraction) {
|
|
|
|
|
+ this.kmap.map.removeInteraction(this.translateInteraction);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 创建拖拽交互
|
|
|
|
|
+ this.translateInteraction = new Translate({
|
|
|
|
|
+ layers: [this.areaLayer.layer],
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 添加拖拽交互到地图
|
|
|
|
|
+ this.kmap.map.addInteraction(this.translateInteraction);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用拖拽交互
|
|
|
|
|
+ disableTranslate() {
|
|
|
|
|
+ if (this.translateInteraction) {
|
|
|
|
|
+ this.kmap.map.removeInteraction(this.translateInteraction);
|
|
|
|
|
+ this.translateInteraction = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ fitView() {
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source || source.getFeatures().length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ let extent = source.getExtent();
|
|
|
|
|
+ // 地图自适应到区域可视范围
|
|
|
|
|
+ this.kmap.getView().fit(extent, { duration: 500, padding: [100, 100, 100, 100] });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 启用编辑模式
|
|
|
|
|
+ startEdit(onModifyCallback) {
|
|
|
|
|
+ if (!this.areaLayer || !this.areaLayer.layer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source || source.getFeatures().length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 保存原始几何数据,用于退出编辑时还原
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ this.originalGeometries = features.map((feature) => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ feature: feature,
|
|
|
|
|
+ geometry: feature.getGeometry().clone(), // 克隆几何数据
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 初始化历史记录(不在进入编辑模式时保存初始状态,在第一次修改时保存)
|
|
|
|
|
+ this.editHistory = [];
|
|
|
|
|
+ this.editHistoryIndex = -1;
|
|
|
|
|
+ // 标记是否已经保存过初始状态
|
|
|
|
|
+ this.hasInitialState = false;
|
|
|
|
|
+ // 标记是否正在执行撤销操作(用于防止撤销时触发保存)
|
|
|
|
|
+ this.isUndoing = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 为所有 feature 设置编辑标记,用于显示编辑点
|
|
|
|
|
+ features.forEach((feature) => {
|
|
|
|
|
+ feature.set("icon", "point-act");
|
|
|
|
|
+ });
|
|
|
|
|
+ // 刷新图层样式
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用拖拽交互(编辑模式下使用 Modify 交互)
|
|
|
|
|
+ this.disableTranslate();
|
|
|
|
|
+
|
|
|
|
|
+ // 设置修改交互的源为area图层
|
|
|
|
|
+ if (this.kmap.modify) {
|
|
|
|
|
+ this.kmap.modify.setActive(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 重新初始化修改交互,使用area图层的源
|
|
|
|
|
+ if (this.editModify) {
|
|
|
|
|
+ this.kmap.map.removeInteraction(this.editModify);
|
|
|
|
|
+ }
|
|
|
|
|
+ this.editModify = new Modify({
|
|
|
|
|
+ source: source,
|
|
|
|
|
+ pixelTolerance: 10,
|
|
|
|
|
+ });
|
|
|
|
|
+ // 保存回调函数引用,用于在事件中调用
|
|
|
|
|
+ this.onModifyCallback = onModifyCallback;
|
|
|
|
|
+
|
|
|
|
|
+ // 监听修改事件,更新样式和保存历史
|
|
|
|
|
+ // 只有在用户真正编辑后(modifyend)才开始记录历史状态
|
|
|
|
|
+ this.editModify.on("modifystart", () => {
|
|
|
|
|
+ // 只有在第一次修改开始时,才保存当前状态作为初始状态
|
|
|
|
|
+ if (!this.hasInitialState) {
|
|
|
|
|
+ this.saveHistoryState();
|
|
|
|
|
+ // 第一次保存后,通知外部更新状态
|
|
|
|
|
+ if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ this.onModifyCallback();
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 后续修改时不在 modifystart 保存,只在 modifyend 保存
|
|
|
|
|
+ });
|
|
|
|
|
+ this.editModify.on("modifyend", () => {
|
|
|
|
|
+ // 修改完成后保存修改后的状态(只有真正编辑后才保存)
|
|
|
|
|
+ if (this.hasInitialState) {
|
|
|
|
|
+ this.saveHistoryState();
|
|
|
|
|
+ }
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ // 调用回调通知外部有修改,使用 setTimeout 确保状态已经更新
|
|
|
|
|
+ if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ this.onModifyCallback();
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ this.kmap.map.addInteraction(this.editModify);
|
|
|
|
|
+ this.editModify.setActive(true);
|
|
|
|
|
+ this.isEditMode = true;
|
|
|
|
|
+
|
|
|
|
|
+ // 启用点选择功能
|
|
|
|
|
+ this.enablePointSelection();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 启用点选择功能
|
|
|
|
|
+ enablePointSelection() {
|
|
|
|
|
+ if (!this.areaLayer || !this.areaLayer.layer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source || source.getFeatures().length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清除之前的点击处理器
|
|
|
|
|
+ if (this.pointClickHandler) {
|
|
|
|
|
+ this.kmap.map.un("singleclick", this.pointClickHandler);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const that = this;
|
|
|
|
|
+ this.pointClickHandler = (evt) => {
|
|
|
|
|
+ if (!that.isEditMode) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const pixel = evt.pixel;
|
|
|
|
|
+
|
|
|
|
|
+ // 获取所有 feature
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ if (features.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 在所有 feature 中查找最近的点
|
|
|
|
|
+ let minDistance = Infinity;
|
|
|
|
|
+ let closestPoint = null;
|
|
|
|
|
+
|
|
|
|
|
+ // 遍历所有 feature 查找点击的点
|
|
|
|
|
+ for (let feature of features) {
|
|
|
|
|
+ if (feature.get("icon") !== "point-act") {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
|
|
+ const type = geometry.getType();
|
|
|
|
|
+ let rings = [];
|
|
|
|
|
+
|
|
|
|
|
+ if (type === "Polygon") {
|
|
|
|
|
+ rings = geometry.getCoordinates();
|
|
|
|
|
+ } else if (type === "MultiPolygon") {
|
|
|
|
|
+ const polys = geometry.getCoordinates();
|
|
|
|
|
+ polys.forEach((poly) => {
|
|
|
|
|
+ rings = rings.concat(poly);
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 遍历所有环查找最近的点
|
|
|
|
|
+ rings.forEach((ring, ringIndex) => {
|
|
|
|
|
+ if (!ring || ring.length < 2) return;
|
|
|
|
|
+
|
|
|
|
|
+ for (let i = 0; i < ring.length - 1; i++) {
|
|
|
|
|
+ const pointCoord = ring[i];
|
|
|
|
|
+ // 计算点击位置与点的距离(像素)
|
|
|
|
|
+ const pointPixel = that.kmap.map.getPixelFromCoordinate(pointCoord);
|
|
|
|
|
+ const distance = Math.sqrt(
|
|
|
|
|
+ Math.pow(pixel[0] - pointPixel[0], 2) + Math.pow(pixel[1] - pointPixel[1], 2)
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ // 记录最近的点
|
|
|
|
|
+ if (distance < minDistance) {
|
|
|
|
|
+ minDistance = distance;
|
|
|
|
|
+ closestPoint = {
|
|
|
|
|
+ feature: feature,
|
|
|
|
|
+ ringIndex: ringIndex,
|
|
|
|
|
+ pointIndex: i,
|
|
|
|
|
+ distance: distance,
|
|
|
|
|
+ };
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 如果找到最近的点且距离小于 15 像素,选中它
|
|
|
|
|
+ if (closestPoint && closestPoint.distance < 15) {
|
|
|
|
|
+ that.selectedPointFeature = closestPoint.feature;
|
|
|
|
|
+ that.selectedPointIndex = closestPoint.pointIndex;
|
|
|
|
|
+ that.selectedRingIndex = closestPoint.ringIndex;
|
|
|
|
|
+
|
|
|
|
|
+ // 刷新图层样式
|
|
|
|
|
+ that.areaLayer.layer.changed();
|
|
|
|
|
+
|
|
|
|
|
+ // 通知外部更新
|
|
|
|
|
+ if (that.onModifyCallback && typeof that.onModifyCallback === "function") {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ that.onModifyCallback();
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有选中点,清除选择
|
|
|
|
|
+ that.clearPointSelection();
|
|
|
|
|
+ if (that.onModifyCallback && typeof that.onModifyCallback === "function") {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ that.onModifyCallback();
|
|
|
|
|
+ }, 0);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 使用 priority 确保点选择事件在 Modify 交互之前处理
|
|
|
|
|
+ // 通过使用 once 和重新绑定,或者使用更高的优先级
|
|
|
|
|
+ this.kmap.map.on("singleclick", this.pointClickHandler);
|
|
|
|
|
+
|
|
|
|
|
+ // 暂时禁用 Modify 交互的点选择功能,避免冲突
|
|
|
|
|
+ // Modify 交互仍然可以用于拖拽编辑,但点选择由我们自己的处理器处理
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清除点选择
|
|
|
|
|
+ clearPointSelection() {
|
|
|
|
|
+ this.selectedPointFeature = null;
|
|
|
|
|
+ this.selectedPointIndex = -1;
|
|
|
|
|
+ this.selectedRingIndex = 0;
|
|
|
|
|
+ if (this.areaLayer && this.areaLayer.layer) {
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有选中的点
|
|
|
|
|
+ hasSelectedPoint() {
|
|
|
|
|
+ return this.selectedPointFeature !== null && this.selectedPointIndex >= 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除选中的点
|
|
|
|
|
+ deleteSelectedPoint() {
|
|
|
|
|
+ if (!this.hasSelectedPoint()) {
|
|
|
|
|
+ console.log("没有选中的点");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const feature = this.selectedPointFeature;
|
|
|
|
|
+ if (!feature) {
|
|
|
|
|
+ console.log("选中的 feature 不存在");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
|
|
+ if (!geometry) {
|
|
|
|
|
+ console.log("几何对象不存在");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const type = geometry.getType();
|
|
|
|
|
+ let coordinates = geometry.getCoordinates();
|
|
|
|
|
+ let updated = false;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 保存选中的索引,因为删除后需要清除选择
|
|
|
|
|
+ const selectedPointIndex = this.selectedPointIndex;
|
|
|
|
|
+ const selectedRingIndex = this.selectedRingIndex;
|
|
|
|
|
+
|
|
|
|
|
+ // 在删除点之前,先保存当前状态(包含要删除的点)
|
|
|
|
|
+ // 这样撤销时可以恢复到删除前的状态
|
|
|
|
|
+ if (!this.hasInitialState) {
|
|
|
|
|
+ this.saveHistoryState();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果已经有初始状态,也需要保存删除前的状态
|
|
|
|
|
+ this.saveHistoryState();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (type === "Polygon") {
|
|
|
|
|
+ // 单多边形
|
|
|
|
|
+ let ring = coordinates[0];
|
|
|
|
|
+ // 删除选中的点(不包括最后一个闭合点)
|
|
|
|
|
+ if (selectedPointIndex < ring.length - 1 && ring.length > 1) {
|
|
|
|
|
+ // 创建新的坐标数组,避免直接修改原数组
|
|
|
|
|
+ const newRing = [...ring];
|
|
|
|
|
+ newRing.splice(selectedPointIndex, 1);
|
|
|
|
|
+ // 确保多边形闭合(最后一个点等于第一个点)
|
|
|
|
|
+ if (
|
|
|
|
|
+ newRing.length > 0 &&
|
|
|
|
|
+ (newRing[newRing.length - 1][0] !== newRing[0][0] ||
|
|
|
|
|
+ newRing[newRing.length - 1][1] !== newRing[0][1])
|
|
|
|
|
+ ) {
|
|
|
|
|
+ newRing[newRing.length - 1] = [newRing[0][0], newRing[0][1]];
|
|
|
|
|
+ }
|
|
|
|
|
+ // 更新几何坐标
|
|
|
|
|
+ geometry.setCoordinates([newRing]);
|
|
|
|
|
+ updated = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ } else if (type === "MultiPolygon") {
|
|
|
|
|
+ // 多多边形
|
|
|
|
|
+ if (selectedRingIndex < coordinates.length) {
|
|
|
|
|
+ let ring = coordinates[selectedRingIndex][0];
|
|
|
|
|
+ if (selectedPointIndex < ring.length - 1 && ring.length > 1) {
|
|
|
|
|
+ // 创建新的坐标数组
|
|
|
|
|
+ const newRing = [...ring];
|
|
|
|
|
+ newRing.splice(selectedPointIndex, 1);
|
|
|
|
|
+ // 确保多边形闭合
|
|
|
|
|
+ if (
|
|
|
|
|
+ newRing.length > 0 &&
|
|
|
|
|
+ (newRing[newRing.length - 1][0] !== newRing[0][0] ||
|
|
|
|
|
+ newRing[newRing.length - 1][1] !== newRing[0][1])
|
|
|
|
|
+ ) {
|
|
|
|
|
+ newRing[newRing.length - 1] = [newRing[0][0], newRing[0][1]];
|
|
|
|
|
+ }
|
|
|
|
|
+ // 更新对应环的坐标
|
|
|
|
|
+ const newCoordinates = JSON.parse(JSON.stringify(coordinates));
|
|
|
|
|
+ newCoordinates[selectedRingIndex][0] = newRing;
|
|
|
|
|
+ geometry.setCoordinates(newCoordinates);
|
|
|
|
|
+ updated = true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!updated) {
|
|
|
|
|
+ console.log("删除点失败:未更新");
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 触发几何变化事件,确保图层知道几何已更新
|
|
|
|
|
+ geometry.changed();
|
|
|
|
|
+ // 触发 feature 变化事件
|
|
|
|
|
+ feature.changed();
|
|
|
|
|
+
|
|
|
|
|
+ // 保存删除点后的状态
|
|
|
|
|
+ this.saveHistoryState();
|
|
|
|
|
+
|
|
|
|
|
+ // 清除选择(必须在更新几何之后)
|
|
|
|
|
+ this.clearPointSelection();
|
|
|
|
|
+
|
|
|
|
|
+ // 刷新图层(这会触发样式函数重新计算面积)
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+
|
|
|
|
|
+ // 确保 Modify 交互仍然激活
|
|
|
|
|
+ if (this.editModify) {
|
|
|
|
|
+ this.editModify.setActive(true);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 通知外部更新(确保撤销按钮状态更新)
|
|
|
|
|
+ // 使用 setTimeout 确保历史状态已经保存完成
|
|
|
|
|
+ if (this.onModifyCallback && typeof this.onModifyCallback === "function") {
|
|
|
|
|
+ setTimeout(() => {
|
|
|
|
|
+ this.onModifyCallback();
|
|
|
|
|
+ }, 10);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ console.log(
|
|
|
|
|
+ "删除点成功,历史记录索引:",
|
|
|
|
|
+ this.editHistoryIndex,
|
|
|
|
|
+ "历史记录长度:",
|
|
|
|
|
+ this.editHistory ? this.editHistory.length : 0,
|
|
|
|
|
+ "可以撤销:",
|
|
|
|
|
+ this.canUndo()
|
|
|
|
|
+ );
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error("删除点失败:", error);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保存当前状态到历史记录
|
|
|
|
|
+ saveHistoryState() {
|
|
|
|
|
+ // 如果正在执行撤销操作,不保存状态(避免撤销时触发保存)
|
|
|
|
|
+ if (this.isUndoing) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.areaLayer || !this.areaLayer.layer) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ if (features.length === 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 初始化历史记录数组
|
|
|
|
|
+ if (!this.editHistory) {
|
|
|
|
|
+ this.editHistory = [];
|
|
|
|
|
+ this.editHistoryIndex = -1;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 保存当前所有feature的几何数据
|
|
|
|
|
+ const state = features.map((feature) => {
|
|
|
|
|
+ return {
|
|
|
|
|
+ feature: feature,
|
|
|
|
|
+ geometry: feature.getGeometry().clone(),
|
|
|
|
|
+ };
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // 如果是第一次保存(还没有历史记录),直接保存初始状态
|
|
|
|
|
+ if (this.editHistory.length === 0) {
|
|
|
|
|
+ this.hasInitialState = true;
|
|
|
|
|
+ // 直接添加到历史记录,不需要检查
|
|
|
|
|
+ this.editHistory.push(state);
|
|
|
|
|
+ this.editHistoryIndex = 0;
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查当前状态是否与上一个状态相同(避免重复保存)
|
|
|
|
|
+ if (this.editHistoryIndex >= 0) {
|
|
|
|
|
+ const lastState = this.editHistory[this.editHistoryIndex];
|
|
|
|
|
+ if (lastState && lastState.length === state.length) {
|
|
|
|
|
+ const wkt = new WKT();
|
|
|
|
|
+ let isSame = true;
|
|
|
|
|
+ for (let i = 0; i < state.length; i++) {
|
|
|
|
|
+ const currentWkt = wkt.writeGeometry(state[i].geometry);
|
|
|
|
|
+ const lastWkt = wkt.writeGeometry(lastState[i].geometry);
|
|
|
|
|
+ if (currentWkt !== lastWkt) {
|
|
|
|
|
+ isSame = false;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果状态相同,不保存
|
|
|
|
|
+ if (isSame) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 移除当前位置之后的历史记录(如果有新的修改)
|
|
|
|
|
+ if (this.editHistoryIndex < this.editHistory.length - 1) {
|
|
|
|
|
+ this.editHistory = this.editHistory.slice(0, this.editHistoryIndex + 1);
|
|
|
|
|
+ }
|
|
|
|
|
+ // 添加到历史记录
|
|
|
|
|
+ this.editHistory.push(state);
|
|
|
|
|
+ this.editHistoryIndex = this.editHistory.length - 1;
|
|
|
|
|
+ // 限制历史记录数量,避免内存占用过大
|
|
|
|
|
+ if (this.editHistory.length > 50) {
|
|
|
|
|
+ this.editHistory.shift();
|
|
|
|
|
+ this.editHistoryIndex--;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 撤销编辑(恢复到上一步)
|
|
|
|
|
+ undoEdit() {
|
|
|
|
|
+ if (!this.isEditMode) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.editHistory || this.editHistory.length === 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果已经在第一个状态(index 0),无法撤销
|
|
|
|
|
+ if (this.editHistoryIndex <= 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 设置撤销标志,防止恢复几何数据时触发保存
|
|
|
|
|
+ this.isUndoing = true;
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 回退到上一个状态
|
|
|
|
|
+ this.editHistoryIndex--;
|
|
|
|
|
+ const previousState = this.editHistory[this.editHistoryIndex];
|
|
|
|
|
+
|
|
|
|
|
+ // 恢复几何数据
|
|
|
|
|
+ if (previousState && previousState.length > 0) {
|
|
|
|
|
+ previousState.forEach(({ feature, geometry }) => {
|
|
|
|
|
+ feature.setGeometry(geometry.clone());
|
|
|
|
|
+ });
|
|
|
|
|
+ // 刷新图层样式
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ return false;
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ // 确保无论成功还是失败,都要重置标志位
|
|
|
|
|
+ this.isUndoing = false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否可以撤销
|
|
|
|
|
+ canUndo() {
|
|
|
|
|
+ if (!this.isEditMode) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (!this.editHistory || this.editHistory.length === 0) {
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 如果当前索引大于0,说明可以撤销
|
|
|
|
|
+ return this.editHistoryIndex > 0;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 禁用编辑模式(还原到编辑前的状态)
|
|
|
|
|
+ endEdit() {
|
|
|
|
|
+ if (this.editModify) {
|
|
|
|
|
+ this.editModify.setActive(false);
|
|
|
|
|
+ this.kmap.map.removeInteraction(this.editModify);
|
|
|
|
|
+ this.editModify = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清除点选择
|
|
|
|
|
+ if (this.pointClickHandler) {
|
|
|
|
|
+ this.kmap.map.un("singleclick", this.pointClickHandler);
|
|
|
|
|
+ this.pointClickHandler = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.clearPointSelection();
|
|
|
|
|
+
|
|
|
|
|
+ // 清除历史记录
|
|
|
|
|
+ this.editHistory = null;
|
|
|
|
|
+ this.editHistoryIndex = -1;
|
|
|
|
|
+ this.hasInitialState = false;
|
|
|
|
|
+ this.isUndoing = false;
|
|
|
|
|
+ // 还原地块到编辑前的状态
|
|
|
|
|
+ if (this.areaLayer && this.areaLayer.layer) {
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (source && this.originalGeometries) {
|
|
|
|
|
+ // 恢复原始几何数据
|
|
|
|
|
+ this.originalGeometries.forEach(({ feature, geometry }) => {
|
|
|
|
|
+ feature.setGeometry(geometry.clone());
|
|
|
|
|
+ feature.unset("icon");
|
|
|
|
|
+ });
|
|
|
|
|
+ // 清除保存的原始几何数据
|
|
|
|
|
+ this.originalGeometries = null;
|
|
|
|
|
+ // 刷新图层样式
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // 如果没有保存的原始数据,只清除编辑标记
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ features.forEach((feature) => {
|
|
|
|
|
+ feature.unset("icon");
|
|
|
|
|
+ });
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.isEditMode = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 重新启用拖拽交互(退出编辑模式后可以拖拽)
|
|
|
|
|
+ this.enableTranslate();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 保存编辑(保留当前编辑状态)
|
|
|
|
|
+ saveEdit() {
|
|
|
|
|
+ if (this.editModify) {
|
|
|
|
|
+ this.editModify.setActive(false);
|
|
|
|
|
+ this.kmap.map.removeInteraction(this.editModify);
|
|
|
|
|
+ this.editModify = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 清除点选择
|
|
|
|
|
+ if (this.pointClickHandler) {
|
|
|
|
|
+ this.kmap.map.un("singleclick", this.pointClickHandler);
|
|
|
|
|
+ this.pointClickHandler = null;
|
|
|
|
|
+ }
|
|
|
|
|
+ this.clearPointSelection();
|
|
|
|
|
+
|
|
|
|
|
+ // 清除保存的原始几何数据(因为已经保存了当前状态)
|
|
|
|
|
+ if (this.areaLayer && this.areaLayer.layer) {
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (source) {
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ features.forEach((feature) => {
|
|
|
|
|
+ feature.unset("icon");
|
|
|
|
|
+ });
|
|
|
|
|
+ // 刷新图层样式
|
|
|
|
|
+ this.areaLayer.layer.changed();
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ this.originalGeometries = null;
|
|
|
|
|
+ this.isEditMode = false;
|
|
|
|
|
+
|
|
|
|
|
+ // 重新启用拖拽交互(保存编辑后可以拖拽)
|
|
|
|
|
+ this.enableTranslate();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取地块的WKT数据
|
|
|
|
|
+ getAreaWKT() {
|
|
|
|
|
+ if (!this.areaLayer || !this.areaLayer.layer) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ const source = this.areaLayer.layer.getSource();
|
|
|
|
|
+ if (!source || source.getFeatures().length === 0) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ const features = source.getFeatures();
|
|
|
|
|
+ const wktFormat = new WKT();
|
|
|
|
|
+ const wktStrings = features
|
|
|
|
|
+ .map((feature) => {
|
|
|
|
|
+ const geometry = feature.getGeometry();
|
|
|
|
|
+ if (!geometry) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 转换为WGS84坐标系(EPSG:4326)的WKT
|
|
|
|
|
+ const geom = geometry.clone();
|
|
|
|
|
+ const mapProjection = this.kmap.map.getView().getProjection();
|
|
|
|
|
+ geom.transform(mapProjection, "EPSG:4326");
|
|
|
|
|
+ return wktFormat.writeGeometry(geom);
|
|
|
|
|
+ })
|
|
|
|
|
+ .filter(Boolean);
|
|
|
|
|
+
|
|
|
|
|
+ return wktStrings.length > 0 ? wktStrings[0] : null;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export default IndexMap;
|