Skip to content

拖拽添加覆盖物

TIP

拖拽下方的图标到地图指定位置可以生成覆盖物。

对着地图上的覆盖物单击右键可以移除。

代码如下:

点我查看代码
vue
<template>
  <div class="top">
    <img
      v-for="(item, index) in imageList"
      :key="index"
      :src="item"
      draggable="true"
      @dragstart="handleDragStart"
    />
  </div>

  <div id="map" @drop="handleDrop" @dragover="handleDragover">
    <div class="menu" ref="menu">
      <div class="menu-item" @click="handleDelete">删除</div>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, ref, Ref, onBeforeUnmount } from "vue";
import { Map, View, Overlay, Feature } from "ol";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { defaults, FullScreen } from "ol/control";
import { XYZ, Vector as VectorSource } from "ol/source";
import { MAPURL, ATTRIBUTIONS } from "../../../constants";
import { defaults as defaultInteractions } from "ol/interaction";
import { Geometry } from "ol/geom";
import { addVectorLabel } from "./tools";
import Drag from "./drag";

let map: Map | null = null,
  menu_overlay: Overlay;
const menu: Ref<HTMLDivElement | null> = ref(null);
const selectIcon: Ref<Feature<Geometry> | null> = ref(null);
const imageList = ["/image/chips.png", "/image/ice_cream.png"];
const raster = new TileLayer({
  source: new XYZ({
    attributions: ATTRIBUTIONS,
    url: MAPURL,
    maxZoom: 20,
  }),
});
//矢量标注的数据源
const vectorSource: VectorSource = new VectorSource();
//矢量标注图层
const vectorLayer = new VectorLayer({
  source: vectorSource,
});
const initMap = () => {
  map = new Map({
    //初始化map
    target: "map",
    //地图容器中加载的图层
    interactions: defaultInteractions().extend([new Drag()]),
    layers: [
      //加载瓦片图层数据
      raster,
      vectorLayer,
    ],
    view: new View({
      projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3 857
      center: [0, 0], // 深圳坐标
      //地图初始显示级别
      zoom: 5,
    }),
    //加载控件到地图容器中
    controls: defaults().extend([
      new FullScreen(), //加载全屏显示控件(目前支持非IE内核浏览器)
    ]),
  });

  menu_overlay = new Overlay({
    element: menu.value || undefined,
    positioning: "center-center",
  });
  map.addOverlay(menu_overlay);
};
const handleDragStart = (event) => {
  const src = event.target.src;
  event.dataTransfer.setData("src", src);
};
const handleDragover = (event: DragEvent) => {
  event.preventDefault();
};
const handleDrop = (event) => {
  if (map === null) return;
  const imageUrl: string = event.dataTransfer?.getData("src");
  const { layerX, layerY }: { layerX: number; layerY: number } = event;
  const coordinate = map.getCoordinateFromPixel([layerX, layerY]);
  addVectorLabel({
    coordinate,
    vectorSource,
    imageUrl,
  });
  event.stopPropagation();
};

//右键函数
/*params: map(创建的map对象)*/
const contextmenu = (map: Map) => {
  map.getViewport().oncontextmenu = function (event) {
    event.preventDefault();
    const pixel = map.getEventPixel(event);
    const feature = map.forEachFeatureAtPixel(pixel, (feature) => feature);
    if (feature) {
      const coordinate = map.getEventCoordinate(event);
      selectIcon.value = feature as Feature<Geometry>;
      menu_overlay.setPosition(coordinate);
    }
  };
};
const handleDelete = () => {
  const source = vectorLayer.getSource();
  if (source === null || selectIcon.value === null) return;
  source.removeFeature(selectIcon.value);
  selectIcon.value = null;
  menu_overlay.setPosition(undefined);
};

onMounted(() => {
  const mapDom = document.getElementById("map");
  //首先禁用document自带的右键功能
  mapDom && (mapDom.oncontextmenu = () => false);
  initMap();
  if (map) {
    contextmenu(map);
    map.on("singleclick", () => {
      menu_overlay.setPosition(undefined);
    });
  }
});

onBeforeUnmount(() => {
  if (map) {
    map.dispose();
    map = null;
  }
});
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#map {
  position: relative;
  height: 650px;
}
.top {
  height: 50px;
  text-align: center;
  line-height: 50px;
}
img {
  display: inline-block;
  width: 20px;
  cursor: pointer;
  margin: 0 20px;
}
.menu {
  position: absolute;
  left: 10px;
  top: 10px;
  width: 60px;
  background-color: #f9f9f9;
  min-width: 160px;
  box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
  z-index: 99;
  border: 1px solid #ccc;
  border-bottom: none;
}
.menu-item {
  text-align: center;
  height: 30px;
  line-height: 30px;
  border-bottom: 1px solid #ccc;
  cursor: pointer;
  color: #000;
}
</style>

tools 代码如下:

点我查看代码
ts
import { Style, Icon } from 'ol/style'
import { Feature } from 'ol'
import { Point } from 'ol/geom'
import { VectorLabelOptions } from '../../../@types'

/**
 * 创建矢量标注样式函数,设置image为图标ol.style.Icon
 * @param {ol.Feature} feature 要素
 */
export const createLabelStyle = (imageUrl: string | undefined): Style =>
  new Style({
    image: new Icon({
      crossOrigin: 'anonymous',
      //图标的url
      src: imageUrl,
      scale: 0.1,
    }),
  })

/**
 * 添加一个新的标注(矢量要素)
 * @param {ol.Coordinate} coordinate 坐标点
 * @param {ol.source.Vector} vectorSource 矢量标注的数据源
 * @param {string} imageUrl 图片地址
 * @param {string} name 标注名
 */
export const addVectorLabel = ({
  coordinate,
  vectorSource,
  imageUrl,
  name = '标注点',
}: VectorLabelOptions) => {
  if (vectorSource === null) return
  //新建一个要素 ol.Feature
  const newFeature = new Feature({
    //几何信息
    geometry: new Point(coordinate),
    //名称属性
    name,
  })
  //设置要素的样式
  newFeature.setStyle(createLabelStyle(imageUrl))
  //将新要素添加到数据源中
  vectorSource.addFeature(newFeature)
}

drag 代码如下:

点我查看代码
ts

import { FeatureLike } from 'ol/Feature'
import { Pointer as PointerInteraction } from 'ol/interaction'
import { Point } from 'ol/geom'
import { Coordinate } from 'ol/coordinate';
import MapBrowserEvent from 'ol/MapBrowserEvent'

export default class Drag extends PointerInteraction {
  coordinate_: Coordinate | null;
  cursor_: string;
  feature_: FeatureLike | null
  previousCursor_: string | undefined
  constructor() {
    super()

    /**
     * @type {import("../src/ol/coordinate.js").Coordinate}
     * @private
     */
    this.coordinate_ = null

    /**
     * @type {string|undefined}
     * @private
     */
    this.cursor_ = 'pointer'

    /**
     * @type {Feature}
     * @private
     */
    this.feature_ = null

    /**
     * @type {string|undefined}
     * @private
     */
    this.previousCursor_ = undefined
  }

  /**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Map browser event.
 * @return {boolean} `true` to start the drag sequence.
 */
  handleDownEvent(evt: MapBrowserEvent<UIEvent>) {
    const map = evt.map

    const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
      return feature
    })

    if (feature) {
      this.coordinate_ = evt.coordinate
      this.feature_ = feature
    }

    return !!feature
  }

  /**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Map browser event.
 */
  handleDragEvent(evt: MapBrowserEvent<UIEvent>) {
    if (this.coordinate_ === null || this.feature_ === null) return
    const deltaX = evt.coordinate[0] - this.coordinate_[0]
    const deltaY = evt.coordinate[1] - this.coordinate_[1]

    const geometry = this.feature_.getGeometry() as Point
    geometry.translate(deltaX, deltaY)

    this.coordinate_[0] = evt.coordinate[0]
    this.coordinate_[1] = evt.coordinate[1]
  }

  /**
 * @param {import("../src/ol/MapBrowserEvent.js").default} evt Event.
 */
  handleMoveEvent(evt: MapBrowserEvent<UIEvent>) {
    if (this.cursor_) {
      const map = evt.map
      const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
        return feature
      })
      const element = evt.map.getTargetElement()
      if (feature) {
        if (element.style.cursor != this.cursor_) {
          this.previousCursor_ = element.style.cursor
          element.style.cursor = this.cursor_
        }
      } else if (this.previousCursor_ !== undefined) {
        element.style.cursor = this.previousCursor_
        this.previousCursor_ = undefined
      }
    }
  }

  /**
   * @return {boolean} `false` to stop the drag sequence.
   */
  handleUpEvent(): boolean {
    this.coordinate_ = null
    this.feature_ = null
    return false
  }

}

如有转载或 CV 的请标注本站原文地址