Loading

openlayers增加移动功能

移动说明

  • 支持移动点或线元素
  • 移动时虚化原来的点(利用translatestarttranslateend事件,在拖拽开始时创建要素副本(虚影),拖拽结束时移除副本。)

思路:
移动的元素放到一个图层(move_vector_source)里去维护

效果图

chrome-capture-2025-11-14 (1)

示例代码

places.json

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "昆明"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          102.7128,
          25.0406
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "name": "贵阳"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          106.7139,
          26.5784
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "name": "广州"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [
          113.280637,
          23.125178
        ]
      }
    }
  ]
}

index.vue

<template>
  <div class="app">
    <div class="box">
      <h3>初始化图形数据</h3>
      <div class="operate-btns">
        <div
          v-for="(item, index) in graph_btn_list"
          :key="index"
          :class="['btn-item']"
          @click="onGraphBtnClick(item)"
        >
          {{ item.name }}
        </div>
      </div>
    </div>
    <div class="box">
      <h3>移动功能</h3>
      <div class="operate-btns">
        <div
          v-for="(item, index) in move_btn_list"
          :key="index"
          :class="['btn-item']"
          @click="onMoveBtnClick(item)"
        >
          {{ item.name }}
        </div>
      </div>
    </div>

    <div class="map-container" ref="map_ref"></div>
  </div>
</template>

<script>
import * as test_ol from "ol";
import GeoJSON from "ol/format/GeoJSON";

import { Map, View, Overlay, Feature } from "ol";
import { XYZ, Vector as SourceVector } from "ol/source";
import { Vector as LayerVector, Tile } from "ol/layer";
import { Text, Fill, Stroke, Circle, Style, Icon } from "ol/style";
import { toLonLat, fromLonLat } from "ol/proj";
import { Draw, Translate } from "ol/interaction";
import { Point, LineString, Polygon } from "ol/geom";

// console.log("Translate ==>", Translate);
const ol = {
  source: {
    Vector: SourceVector,
    XYZ,
  },
  format: {
    GeoJSON,
  },
  style: {
    Text,
    Fill,
    Stroke,
    Circle,
    Style,
    Icon,
  },
  layer: {
    Vector: LayerVector,
    Tile,
  },
  proj: {
    toLonLat,
    fromLonLat,
  },
  interaction: {
    Draw,
    Translate,
  },
  Overlay,
  Feature,
  geom: {
    Point,
    LineString,
    Polygon,
  },
};

export default {
  data() {
    return {
      map: null,
      zoom: 6,
      // 控制标注显示的最小缩放级别
      label_zoom_level: 5,
      draw_btn_list: [
        {
          name: "点",
          alias: "point",
          type: "Point",
        },
        {
          name: "线",
          alias: "line",
          type: "LineString",
        },
        {
          name: "多边形",
          alias: "polygon",
          type: "Polygon",
        },
        {
          name: "清除所有绘制",
          alias: "clear",
          type: "Clear",
        },
      ],
      active_draw_btn_index: -1,
      // 绘制的实例
      draw_instance: null,
      // 存放矢量数据源和图层实例
      draw_vector_source: null,
      graph_btn_list: [
        {
          name: "点",
          alias: "point",
        },
        {
          name: "线",
          alias: "line",
        },
        {
          name: "多边形",
          alias: "polygon",
        },
      ],
      // 初始化图形的数据源和图层
      init_vector_source: null, // 添加初始化图形数据源
      init_vector_layer: null, // 添加初始化图形图层

      move_btn_list: [
        {
          name: "点",
          alias: "point",
        },
        {
          name: "线",
          alias: "line",
        },
      ],

      // 移动图层
      move_vector_source: null,
      move_vector_layer: null,
      moveFeature: null,
    };
  },
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      const that = this;
      // 创建卫星图层
      const satelliteLayer = new ol.layer.Tile({
        source: new ol.source.XYZ({
          // 这里使用高德影像图层
          url: "http://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
          maxZoom: 18,
        }),
        opacity: 0.9,
        zIndex: 1,
      });

      const map = new Map({
        target: this.$refs.map_ref,
        layers: [satelliteLayer],
        view: new View({
          // 113.24269373938198,23.1299597462194
          center: ol.proj.fromLonLat([113.24269373938198, 23.1299597462194]),
          zoom: this.zoom,
        }),
      });
      this.map = map;

      // 初始化绘制图层
      var vectorSource = new ol.source.Vector({
        url: "/assets/json/places.json",
        format: new ol.format.GeoJSON(),
      });
      var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
          // 为每个地名要素设置样式,包括点样式和文字标签
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "bold 12px Arial",
            fill: new ol.style.Fill({
              color: "#fff",
            }),
            stroke: new ol.style.Stroke({
              color: "#000",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
              stroke: new ol.style.Stroke({
                color: "#ffffff",
                width: 2,
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
        zIndex: 999,
      });
      map.addLayer(vectorLayer);

      // 初始化图形图层
      const init_vector_source = new ol.source.Vector();
      const init_vector_layer = new ol.layer.Vector({
        source: init_vector_source,

        // 动态设置标记
        style: function (feature) {
          // 检查几何类型
          const geometry = feature.getGeometry();
          const graph = geometry.getType();
          if (graph === "Point") {
            // 获取要素的图片URL
            const img_config = feature.get("img_config");

            // 如果有图片URL则使用图标,否则使用默认的圆形
            if (img_config.url || img_config.scale) {
              return new ol.style.Style({
                image: new ol.style.Icon({
                  src: img_config.url,
                  scale: img_config.scale || 0.8,
                  anchor: [0.5, 1], // 图标锚点设置在底部中心
                }),
              });
            } else {
              // 默认圆形标记
              return new ol.style.Style({
                image: new ol.style.Circle({
                  radius: 8,
                  fill: new ol.style.Fill({ color: "#ff0000" }),
                  stroke: new ol.style.Stroke({ color: "white", width: 2 }),
                }),
              });
            }
          } else if (graph == "LineString") {
            // 获取线配置
            const line_config = Object.assign(
              {
                strokeColor: "#f00",
                width: 3,
              },
              feature.get("line_config")
            );
            const stroke = new ol.style.Stroke({
              color: line_config.strokeColor,
              width: line_config.width || 3,
            });
            return new ol.style.Style({
              fill: new ol.style.Fill({ color: "rgba(255, 0, 0, 0.3)" }),
              stroke,
            });
          } else if (graph == "Polygon") {
            // 获取多边形配置
            const polygon_config = Object.assign(
              {
                bgColor: "rgba(255, 0, 0, 0.3)",
                strokeColor: "#f00",
                width: 3,
              },
              feature.get("polygon_config")
            );
            const stroke = new ol.style.Stroke({
              color: polygon_config.strokeColor,
              width: polygon_config.width || 3,
            });
            const fill = new ol.style.Fill({ color: polygon_config.bgColor });
            return new ol.style.Style({
              fill,
              stroke,
            });
          } else {
            // 线和多边形保持原有样式
            return new ol.style.Style({
              fill: new ol.style.Fill({ color: "rgba(255, 0, 0, 0.3)" }),
              stroke: new ol.style.Stroke({ color: "#ff0000", width: 3 }),
            });
          }
        },
        zIndex: 998,
      });
      this.init_vector_source = init_vector_source;
      this.init_vector_layer = init_vector_layer;
      map.addLayer(init_vector_layer);

      map.on("click", function (evt) {
        var coordinate = evt.coordinate;
        // 这里只是模拟,实际需根据点击位置获取街景资源并展示
        // console.log("点击位置经纬度:" + ol.proj.toLonLat(coordinate));

        // 检查点击位置是否有要素
        map.forEachFeatureAtPixel(evt.pixel, function (feature) {
          // 核心过滤逻辑:排除临时要素(只有带 isFinished: true 的才是正式要素)
          // 如果点击的是多边形
          // if (feature.get("isFinished") && feature.getGeometry() instanceof ol.geom.Polygon) {
          //   showPopup(evt.coordinate, feature);
          //   return true; // 停止遍历
          // }
          if (feature.get("isFinished")) {
            // console.log("ok");
            that.showPopup(evt.coordinate, feature);
            return true; // 停止遍历
          }
        });
      });

      function updateLabelsVisibility() {
        var currentZoom = map.getView().getZoom();
        that.zoom = currentZoom;

        vectorLayer.setVisible(currentZoom >= that.label_zoom_level);
      }
      updateLabelsVisibility();

      map.on("moveend", updateLabelsVisibility);

      // 绘制
      // 创建矢量数据源和图层
      const vector_source = new ol.source.Vector();
      // 创建矢量图层并关联数据源
      const vector_layer = new ol.layer.Vector({
        source: vector_source,
        style: new ol.style.Style({
          fill: new ol.style.Fill({ color: "rgba(0, 153, 255, 0.2)" }),
          stroke: new ol.style.Stroke({ color: "#0099ff", width: 2 }),
          image: new ol.style.Circle({
            radius: 6,
            fill: new ol.style.Fill({ color: "#0099ff" }),
            stroke: new ol.style.Stroke({ color: "white", width: 1 }),
          }),
        }),
        zIndex: 999,
      });
      this.draw_vector_source = vector_source;
      map.addLayer(vector_layer);

      // 创建移动图层
      var moveVectorSource = new ol.source.Vector();
      var moveVectorLayer = new ol.layer.Vector({
        source: moveVectorSource,
        style: function (feature) {
          // 为每个地名要素设置样式,包括点样式和文字标签
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "bold 12px Arial",
            fill: new ol.style.Fill({
              color: "#fff",
            }),
            stroke: new ol.style.Stroke({
              color: "#000",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
              stroke: new ol.style.Stroke({
                color: "#ffffff",
                width: 2,
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
        zIndex: 9999,
      });
      this.move_vector_source = moveVectorSource;
      this.move_vector_layer = moveVectorLayer;
      map.addLayer(moveVectorLayer);
      // // 创建拖拽交互
      const translateInteraction = new ol.interaction.Translate({
        // 只允许拖拽当前数据源中的要素
        // sources: [moveVectorSource],
        // sources: [this.move_vector_source],
        // 可选:通过filter限制可拖拽的要素类型
        filter: (feature, layer) => {
          // 只允许点要素被拖拽
          // return feature.getGeometry() instanceof ol.geom.Point;
          return layer === this.move_vector_layer && feature === this.moveFeature;
        },
      });

      // // 添加拖拽交互到地图
      map.addInteraction(translateInteraction);

      // 存储虚影要素的变量
      let ghostFeature = null;
      // 定义虚化虚影的样式
      const ghostPointStyle = new ol.style.Style({
        image: new ol.style.Circle({
          radius: 10,
          fill: new ol.style.Fill({ color: "rgba(100, 100, 100, 0.3)" }), // 半透明灰色
          stroke: new ol.style.Stroke({ color: "rgba(255, 255, 255, 0.5)", width: 1 }),
        }),
      });
      const ghostLineStyle = new ol.style.Style({
        stroke: new ol.style.Stroke({
          color: "rgba(0, 0, 0, 0.8)", // 半透明灰色
          width: 2,
          lineDash: [5, 5], // 虚线样式
        }),
      });

      // 拖拽开始时创建虚影
      translateInteraction.on("translatestart", (event) => {
        const feature = event.features.item(0);
        const geometry = feature.getGeometry();
        const geometry_type = geometry.getType();
        // 只处理点的拖拽
        if (feature === this.moveFeature) {
          // 复制当前要素作为虚影
          ghostFeature = this.moveFeature.clone();
          if (geometry_type == "Point") {
            // 设置虚影样式
            ghostFeature.setStyle(ghostPointStyle);
          } else if (geometry_type == "LineString") {
            // 设置虚影样式(半透明)
            ghostFeature.setStyle(ghostLineStyle);
          }

          // // 添加虚影到数据源
          this.move_vector_source.addFeature(ghostFeature);
        }
      });
      // // 监听拖拽结束事件
      translateInteraction.on("translateend", (event) => {
        const feature = event.features.item(0); // 获取被拖拽的要素
        if (ghostFeature) {
          // 从数据源中移除虚影
          this.move_vector_source.removeFeature(ghostFeature);
          ghostFeature = null;
        }
        // 获取新坐标(经纬度)
        const geometry = feature.getGeometry();
        const geometry_type = geometry.getType();
        if (geometry_type === "LineString") {
          // 线的坐标数组
          const lineCoords = geometry
            .getCoordinates()
            .map((coord) => ol.proj.toLonLat(coord))
            .map((coord) => `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`);

          console.warn("拖拽结束:", `新路径点: [\n  ${lineCoords.join(",\n  ")}\n]`);
        } else if (geometry_type == "Point") {
          // 点的情况
          const coords = ol.proj.toLonLat(feature.getGeometry().getCoordinates());
          console.warn(
            "拖拽结束:",
            `新坐标:经度 ${coords[0].toFixed(6)}, 纬度 ${coords[1].toFixed(6)}`
          );
        }
      });
    },

    onGraphBtnClick(item) {
      this.initGraph(item.alias);
    },
    initGraph(type) {
      // 根据不同类型创建对应的几何图形
      switch (type) {
        case "point":
          this.initPointGraph();
          break;

        case "line":
          this.initLineGraph();
          break;

        case "polygon":
          this.initPolygonGraph();
          break;
      }
    },
    initPointGraph() {
      const test_point_data = [
        {
          longitude: 113.746262,
          latitude: 23.050419,
          name: "黄花风铃木林",
          image_url: require("@/assets/imgs/1.png"),
          image_scale: 0.2,
        },
        {
          longitude: 113.89061,
          latitude: 22.882174,
          name: "松山湖科学公园",
        },
      ];
      test_point_data.forEach((item) => {
        const coord = ol.proj.fromLonLat([item.longitude, item.latitude]);
        const pointGeometry = new ol.geom.Point(coord);
        const feature = new ol.Feature({
          geometry: pointGeometry,
          test_data: {
            name: "初始化点",
            age: 25,
            friend: ["张三", "李四"],
          },
          // 添加图片URL到要素属性中
          img_config: {
            url: item.image_url,
            scale: item.image_scale,
          },
          // imageUrl: item.imageUrl,
        });
        feature.set("isFinished", true);
        // 将要素添加到绘制图层的数据源中
        this.init_vector_source.addFeature(feature);
      });
    },
    initLineGraph() {
      const test_line_data = [
        {
          name: "线条测试-1",
          list: [
            {
              longitude: 113.746262,
              latitude: 23.050419,
              name: "黄花风铃木林",
            },
            {
              longitude: 113.89061,
              latitude: 22.882174,
              name: "松山湖科学公园",
            },
          ],
          line_config: {
            strokeColor: "#0f0",
          },
        },
      ];

      test_line_data.forEach((item) => {
        const line_arr = [];
        item.list.forEach((l) => {
          const coord = ol.proj.fromLonLat([l.longitude, l.latitude]);
          line_arr.push(coord);
        });
        const lineGeometry = new ol.geom.LineString(line_arr);
        const feature = new ol.Feature({
          geometry: lineGeometry,
          test_data: {
            name: "初始化线",
            age: 25,
            friend: ["张三", "李四"],
          },
          line_config: item.line_config,
        });
        feature.set("isFinished", true);
        // 将要素添加到绘制图层的数据源中
        this.init_vector_source.addFeature(feature);
      });
    },
    initPolygonGraph() {
      const test_polygon_data = [
        {
          name: "多边形测试-1",
          list: [
            {
              longitude: 113.746262,
              latitude: 23.050419,
              name: "黄花风铃木林",
            },
            {
              longitude: 113.89061,
              latitude: 22.882174,
              name: "松山湖科学公园",
            },
            {
              longitude: 113.89061,
              latitude: 22.891999,
              name: "游船码头",
            },
            {
              longitude: 113.746262,
              latitude: 23.050419,
              name: "黄花风铃木林",
            },
          ],
          polygon_config: {
            bgColor: "#00f",
            strokeColor: "#ff0",
          },
        },
      ];
      test_polygon_data.forEach((item) => {
        const polygon_arr = [];
        item.list.forEach((l) => {
          const coord = ol.proj.fromLonLat([l.longitude, l.latitude]);
          polygon_arr.push(coord);
        });
        const polygonGeometry = new ol.geom.Polygon([polygon_arr]);
        const feature = new ol.Feature({
          geometry: polygonGeometry,
          test_data: {
            name: "初始化多边形",
            age: 25,
            friend: ["张三", "李四"],
          },
          polygon_config: item.polygon_config,
        });
        feature.set("isFinished", true);
        // 将要素添加到绘制图层的数据源中
        this.init_vector_source.addFeature(feature);
      });
    },
    onMoveBtnClick({ alias }) {
      if (alias == "point") {
        // 创建一个点要素
        const pointFeature = new ol.Feature({
          geometry: new ol.geom.Point(
            ol.proj.fromLonLat([114.280637, 23.125178]) // 点的初始位置
          ),
          name: "可移动点",
        });
        const pointFeature2 = new ol.Feature({
          geometry: new ol.geom.Point(
            ol.proj.fromLonLat([116.280637, 23.125178]) // 点的初始位置
          ),
          name: "不可移动点",
        });
        // 设置点的样式
        pointFeature.setStyle(
          new ol.style.Style({
            image: new ol.style.Circle({
              radius: 8,
              fill: new ol.style.Fill({ color: "red" }),
              stroke: new ol.style.Stroke({ color: "white", width: 2 }),
            }),
            text: new ol.style.Text({
              text: "移动点",
              fill: new ol.style.Fill({
                color: "yellow",
              }),
            }),
          })
        );
        this.move_vector_source.addFeature(pointFeature);
        this.move_vector_source.addFeature(pointFeature2);
        // 声明移动的对象
        this.moveFeature = pointFeature;
      } else if (alias == "line") {
        // 创建线的坐标点数组
        const lineCoordinates = [
          ol.proj.fromLonLat([113.746262, 23.050419]), // 起点
          ol.proj.fromLonLat([113.89061, 22.882174]), // 中间点
          ol.proj.fromLonLat([114.280637, 23.125178]), // 终点
        ];

        // 创建线几何对象
        const lineGeometry = new ol.geom.LineString(lineCoordinates);

        // 创建线要素
        const lineFeature = new ol.Feature({
          geometry: lineGeometry,
          name: "示例线",
          description: "这是一条测试线",
        });

        // 设置线的样式
        lineFeature.setStyle(
          new ol.style.Style({
            stroke: new ol.style.Stroke({
              color: "#00ff00", // 线条颜色 (绿色)
              width: 3, // 线条宽度
              lineDash: [5, 5], // 虚线样式 (可选)
            }),
            // 可选:在线的端点或中间点添加图标
            // image: new ol.style.Circle({
            //   radius: 5,
            //   fill: new ol.style.Fill({ color: '#ff0000' })
            // })
          })
        );

        // 将线要素添加到数据源中
        this.move_vector_source.addFeature(lineFeature);
        this.moveFeature = lineFeature;
      }
    },
  },
};
</script>

<style lang="less" scoped>
.app {
  padding: 10px;
}
.box {
  margin-bottom: 10px;
  background-color: #fff;
  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
  padding: 10px;
  border-radius: 5px;
  h3 {
    margin-bottom: 10px;
  }
}
.map-container {
  height: calc(100vh - 50px);
  // background-color: orange;
}
.operate-btns {
  display: flex;
  .btn-item {
    margin: 0 10px 10px 0;
    padding: 8px 12px;
    background: #f5f5f5;
    border: 1px solid #ddd;
    border-radius: 3px;
    cursor: pointer;
    transition: all 0.2s;
    font-size: 14px;
    &:hover {
      background-color: #e0e0e0;
    }
    &.active {
      background-color: #e0e0e0;
    }
  }
}

/* 信息弹窗样式 */
.popup {
  position: absolute;
  background-color: white;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 5px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
  pointer-events: auto;
  display: none;
  .popup-content {
    min-width: 200px;
  }
  .popup-closer {
    position: absolute;
    top: 5px;
    right: 5px;
    cursor: pointer;
    font-size: 16px;
  }
}
</style>


posted @ 2025-11-14 22:51  ^Mao^  阅读(8)  评论(0)    收藏  举报