目录

前言

一、安装ol

二、附上源码

三、附上使用的静态图片

四、给地图添加底图图层,旋转和全屏。设置最大最小放大倍数

五、给底图添加图标图层

六、给地图添加线段

七、给地图添加当前鼠标坐标,悬浮事件

八、给地图添加当前鼠标点击事件

后记


前言

最近项目上要使用openlayers做业务需求,过了这么久终于有时间闲下来写文章了。

使用的ol版本:^6.5.0

使用的版本不同调用openlayers的Api会有变化,请注意。

一、安装ol

npm install ol

修改package.json的版本与我的一致,删除本地package-lock.json

重新安装依赖

npm install

二、附上源码

<template>
  <div style="width: calc(100vw - 488px);overflow: auto;height: 103.4vh;" id="myMap" ref="myMap"></div>
</template>

<style>
</style>

<script lang="ts">
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import ImageLayer from "ol/layer/Image";
import {ImageStatic} from "ol/source";
import {Projection} from "ol/proj";
import {getCenter} from "ol/extent";
import * as olInteraction from 'ol/interaction'
import DragRotateAndZoom from 'ol/interaction/DragRotateAndZoom'
import VectorSource from "ol/source/Vector";
import VectorLayer from "ol/layer/Vector";
import {LineString, Point} from "ol/geom";
import {createStringXY} from "ol/coordinate";
import {FullScreen, MousePosition} from "ol/control";
import {defaults as defaultControls} from "ol/control";
import Zoom from "ol/control/Zoom";
import {Icon, Style, Text} from 'ol/style';
import Fill from "ol/style/Fill";
import Feature from 'ol/Feature';
import Stroke from "ol/style/Stroke";


export default {
  name: "olMap",
  data() {
    return {
      map: null, // 地图
      imgx: 1432, // 当前地图宽
      imgy: 1002, // 当前地图高
      // 图标管理器
      iconLayer: "",
      // 鼠标坐标管理器
      mousePositionControl: "",
      // 鼠标悬浮图层
      hoverFeature: null,
      vectorSource: null,
      // 图标集合
      featureList: [],
      // 轮询显示图标设备名称定时器
      showDeviceTimer: null,
      // 设备集合
      deviceList: [
        {
          x: 400,
          y: 500,
          zIndex: 2,
          imgUrl: require('../assets/1612261527776.png'),
          deviceName: "精密空调",
          deviceId: 1,
          alarmFlag: false
        }, {
          x: 430,
          y: 320.,
          zIndex: 2,
          imgUrl: require('../assets/1612261527776.png'),
          deviceName: "办公室门禁",
          deviceId: 2,
          alarmFlag: false
        }, {
          x: 600,
          y: 600,
          // 层级
          zIndex: 2,
          imgUrl: require('../assets/1612261527776.png'),
          deviceName: "消防烟感1",
          deviceId: 3,
          alarmFlag: true
        }, {
          x: 620,
          y: 600,
          zIndex: 2,
          imgUrl: require('../assets/1612261527776.png'),
          deviceName: "消防烟感2",
          deviceId: 4,
          alarmFlag: true
        }
      ]
    };
  },
  mounted() {
    this.initMap();

  },
  methods: {
    initMap() {
      this.imgy = this.$refs.myMap.offsetHeight
      this.imgx = this.$refs.myMap.offsetWidth
      let extent = [0, 0, this.imgx, this.imgy];  // 获取图片的宽高
      // [minx, miny, maxx, maxy].
      let projection = new Projection({
        code: "xkcd-image",
        units: "pixels",
        extent: extent
      });
      this.map = new Map({
        target: "myMap",
        // shift控制旋转
        interactions: olInteraction.defaults().extend([new DragRotateAndZoom()]),
        layers: [
          new ImageLayer({
            source: new ImageStatic({
              url: require('../assets/1650275911807.jpg'), // 静态地图
              projection: projection,
              imageExtent: extent
            })
          })
        ],
        // 缩放等级zoom当前缩放程度,maxZoom最大缩放程度
        view: new View({
          projection: projection,
          center: getCenter(extent),
          // 当前缩放倍数
          zoom: 2.5,
          // 最大缩放倍数
          maxZoom: 7,
          // 最小缩放倍数
          minZoom: 1
        }),
        controls: defaultControls({
          rotate: true,
        }).extend([
          new FullScreen({
            tipLabel: "全屏"
          }),
          new Zoom({
            zoomInTipLabel: "放大",
            zoomOutTipLabel: "缩小"
          })
        ])
      });
      /**
       * 添加图标
       */
      this.deviceList.forEach(item => {
        this.addDevicePoint(item);
      })
      const vectorSource = new VectorSource()
      this.iconLayer = new VectorLayer({
        source: vectorSource
      })
      // 将图标数组添加到图层中
      this.iconLayer.getSource().addFeatures(this.featureList)
      // 添加图层
      this.map.addLayer(this.iconLayer)
      this.pollShowDeviceText();
      this.mapOver();
      this.mapClick();
      this.mapResolution();
      this.addMousePosition();
      this.addInteraction();
      // 修改旋转鼠标悬浮tip
      document.getElementsByClassName('ol-rotate-reset')[0].attributes.title.nodeValue = '重置旋转'
    },
    // 添加鼠标悬浮地图中坐标
    addMousePosition() {
      this.removeMousePosition()
      this.mousePositionControl = new MousePosition({
        coordinateFormat: function (e) { // 这里格式化成 X: **  Y: **
          // 将坐标保留4位小数位,并转换为字符串
          let stringifyFunc = createStringXY(4)
          let str = stringifyFunc(e)
          return 'X: ' + str.split(',')[0] + '&nbsp;' + ' Y: ' + str.split(',')[1]
        },
        projection: 'EPSG:4326', // 和地图坐标系保持一致
        className: 'custom-mouse-position', // css类名
        target: document.getElementById('mousePosition') // 显示位置鼠标坐标位置DOM
      })
      // 添加控制控件到地图上即可
      this.map.addControl(this.mousePositionControl)
    },
    // 移除鼠标位置
    removeMousePosition() {
      if (this.mousePositionControl) {
        // 移除
        this.map.removeControl(this.mousePositionControl)
        this.mousePositionControl = null
      }
    },
    // 添加图标
    addDevicePoint(item) {
      // 设置图片位置
      const iconFeature = new Feature({
        geometry: new Point([item.x, item.y])
      })
      // 设置样式,这里使用图片
      iconFeature.setStyle([
        new Style({
          image: new Icon({
            // 指定锚 x 值的单位。的值'fraction'表示 x 值是图标的一部分。的值'pixels'表示以像素为单位的 x 值。
            anchorXUnits: 'fraction',
            // 左下角为原点
            anchorOrigin: 'bottom-left',
            anchorYUnits: 'fraction',
            anchor: [0.5, 0],
            // src: item.imgUrl
            src: item.imgUrl
          }),
          zIndex: item.zIndex
        }),
        new Style({
          text: new Text({
            text: '',
            font: "14px Microsoft YaHei",
            offsetY: -15,
            offsetX: 15,
            padding: [5, 10, 5, 15],
            textAlign: 'left',
            backgroundFill: new Fill({
              // eslint-disable-next-line no-constant-condition
              color: item.alarmFlag ? 'rgba(249, 88, 87, 0.5)' : 'rgba(0,111,255,0.5)'//提示背景色
            }),
            fill: new Fill({
              color: "#fff"
            }),
          }),
          zIndex: item.zIndex
        }),
      ])
      // 给图层设置设备图标name和id属性,用作独立 区分
      iconFeature.name = item.deviceName
      iconFeature.id_ = item.deviceId;
      // 将图片Feature添加到Source
      this.featureList.push(iconFeature)
    },
    // 鼠标悬停
    mapOver() {
      const _this = this
      this.map.on('pointermove', evt => {
        // 手指效果
        var pixel = _this.map.getEventPixel(evt.originalEvent);
        var hit = _this.map.hasFeatureAtPixel(pixel);
        // 高亮区域取消手指效果
        const hoverFeature = _this.map.getFeaturesAtPixel(pixel);
        this.$refs.myMap.style.cursor = hit && hoverFeature.length >= 1 ? 'pointer' : '';
        var feature = _this.map.forEachFeatureAtPixel(evt.pixel,
            function (feature) {
              return feature
            })
        if (feature !== undefined) {
          this.restoreDeviceText()
          // 当移入图标时
          // 鼠标移入更改值
          feature.style_[1].text_.text_ = feature.name
          // 鼠标移入更改图标层级为优先级
          feature.style_[0].zIndex_ = 3
          feature.style_[1].zIndex_ = 3
          // 设置当前悬浮图层
          this.hoverFeature = feature
          this.hoverFeature.changed()

          // 停掉轮询定时器
          clearInterval(this.showDeviceTimer)
          this.showDeviceTimer = null;
        } else {
          // 当移出图标时
          if (this.hoverFeature) {
            // 鼠标移入更改图标层级为优先级
            this.hoverFeature.style_[0].zIndex_ = 1
            this.hoverFeature.style_[1].zIndex_ = 1
            // 重启轮询定时器
            if (!this.showDeviceTimer) {
              // 将设备tip标签值设置为空
              this.restoreDeviceText()
              this.pollShowDeviceText();
            }
          }
        }
      })
    },
    // 监听地图点击事件
    mapClick() {
      var _this = this;
      this.map.on('singleclick', evt => {
        var feature = _this.map.forEachFeatureAtPixel(evt.pixel,
            function (feature) {
              return feature
            })
        console.log("feature:", feature)
      })
    },
    // 监听地图缩放
    mapResolution() {
      this.map.getView().on('change:resolution', evt => {
        console.log("缩放了!", evt)
        console.log("当前zoom:" + this.map.getView().getZoom())
        if(this.map.getView().getZoom()==7){
          this.map.getView().setResolution(1)
        }
      })
    },
    // 轮询显示图标设备名称
    pollShowDeviceText() {
      let FeatrueList = this.iconLayer.getSource().getFeatures()
      let allSize = FeatrueList.length - 1;
      let currentSize = 0;
      /* start
       * 这里必须初始化一遍hoverFeature,不然在定时器延时等待过程中会出现拿不到hoverFeature的情况
       * */
      if (this.hoverFeature) {
        this.restoreDeviceText()
      }
      this.hoverFeature = FeatrueList[currentSize]
      this.hoverFeature.style_[1].text_.text_ = this.hoverFeature.name
      this.hoverFeature.changed()
      currentSize++;
      /* end * */
      this.showDeviceTimer = setInterval(() => {
        if (this.hoverFeature) {
          this.restoreDeviceText()
        }
        if (currentSize > allSize) {
          currentSize = 0;
        }
        this.hoverFeature = FeatrueList[currentSize]
        this.hoverFeature.style_[1].text_.text_ = this.hoverFeature.name
        this.hoverFeature.changed()
        currentSize++;
      }, 3000)
    },
    // 将设备tip标签值设置为空
    restoreDeviceText() {
      this.hoverFeature.style_[1].text_.text_ = ''
      this.hoverFeature.changed()
      this.hoverFeature = null;
    },
    // 添加线段
    addInteraction() {
      let featureLine = new Feature({
        geometry: new LineString([
          [480, 630],
          [900, 600],
        ])
      });
      featureLine.setStyle(new Style({
        stroke: new Stroke({
          color: 'rgba(197,19,19,0.7)',
          width: 5
        })
      }))
      let source = new VectorSource()
      source.addFeature(featureLine)
      let layer = new VectorLayer()
      layer.setSource(source)
      this.map.addLayer(layer)
    }
  },
  destroyed() {
    clearInterval(this.showDeviceTimer)
    this.showDeviceTimer = null;
  }
}
</script>

<style scoped>
/deep/ .ol-rotate {
  /* 设置地图旋转回正按钮位置,避免与全屏按钮重叠*/
  right: 3em;
}
</style>

三、附上使用的静态图片

 

四、给地图添加底图图层,旋转和全屏。设置最大最小放大倍数

this.imgy = this.$refs.myMap.offsetHeight
      this.imgx = this.$refs.myMap.offsetWidth
      let extent = [0, 0, this.imgx, this.imgy];  // 获取图片的宽高
      // [minx, miny, maxx, maxy].
      let projection = new Projection({
        code: "xkcd-image",
        units: "pixels",
        extent: extent
      });
      this.map = new Map({
        target: "myMap",
        // shift控制旋转
        interactions: olInteraction.defaults().extend([new DragRotateAndZoom()]),
        layers: [
          new ImageLayer({
            source: new ImageStatic({
              url: require('../assets/1650275911807.jpg'), // 静态地图
              projection: projection,
              imageExtent: extent
            })
          })
        ],
        // 缩放等级zoom当前缩放程度,maxZoom最大缩放程度
        view: new View({
          projection: projection,
          center: getCenter(extent),
          // 当前缩放倍数
          zoom: 2.5,
          // 最大缩放倍数
          maxZoom: 7,
          // 最小缩放倍数
          minZoom: 1
        }),
        controls: defaultControls({
          rotate: true,
        }).extend([
          new FullScreen({
            tipLabel: "全屏"
          }),
          new Zoom({
            zoomInTipLabel: "放大",
            zoomOutTipLabel: "缩小"
          })
        ])
      });

五、给底图添加图标图层

这里有多个图标,实际业务上是获取设备集合。所以这里使用Feature,在data数据源里边创建一个全局的Features集合,每次加一个图标就放进这个集合里边。

addDevicePoint(item) {
      // 设置图片位置
      const iconFeature = new Feature({
        geometry: new Point([item.x, item.y])
      })
      // 设置样式,这里使用图片
      iconFeature.setStyle([
        new Style({
          image: new Icon({
            // 指定锚 x 值的单位。的值'fraction'表示 x 值是图标的一部分。的值'pixels'表示以像素为单位的 x 值。
            anchorXUnits: 'fraction',
            // 左下角为原点
            anchorOrigin: 'bottom-left',
            anchorYUnits: 'fraction',
            anchor: [0.5, 0],
            // src: item.imgUrl
            src: item.imgUrl
          }),
          zIndex: item.zIndex
        }),
        new Style({
          text: new Text({
            text: '',
            font: "14px Microsoft YaHei",
            offsetY: -15,
            offsetX: 15,
            padding: [5, 10, 5, 15],
            textAlign: 'left',
            backgroundFill: new Fill({
              // eslint-disable-next-line no-constant-condition
              color: item.alarmFlag ? 'rgba(249, 88, 87, 0.5)' : 'rgba(0,111,255,0.5)'//提示背景色
            }),
            fill: new Fill({
              color: "#fff"
            }),
          }),
          zIndex: item.zIndex
        }),
      ])
      // 给图层设置设备图标name和id属性,用作独立 区分
      iconFeature.name = item.deviceName
      iconFeature.id_ = item.deviceId;
      // 将图片Feature添加到Source
      this.featureList.push(iconFeature)
    },

最后用这个getSource().addFeatures。同时给整个图标图层添加数据源。

到此整个地图已经有两层图层。

第一个是地图底图图片。

第二个是图标图层。

const vectorSource = new VectorSource()
this.iconLayer = new VectorLayer({
    source: vectorSource
})
// 将图标数组添加到图层中
this.iconLayer.getSource().addFeatures(this.featureList)
// 添加图层
this.map.addLayer(this.iconLayer)

六、给地图添加线段

线段这里也作为单独图层,叫线段图层

    addInteraction() {
      let featureLine = new Feature({
        geometry: new LineString([
          [480, 630],
          [900, 600],
        ])
      });
      featureLine.setStyle(new Style({
        stroke: new Stroke({
          color: 'rgba(197,19,19,0.7)',
          width: 5
        })
      }))
      let source = new VectorSource()
      source.addFeature(featureLine)
      let layer = new VectorLayer()
      layer.setSource(source)
      this.map.addLayer(layer)
    }

七、给地图添加当前鼠标坐标,悬浮事件

mapOver() {
      const _this = this
      this.map.on('pointermove', evt => {
        // 手指效果
        var pixel = _this.map.getEventPixel(evt.originalEvent);
        var hit = _this.map.hasFeatureAtPixel(pixel);
        // 高亮区域取消手指效果
        const hoverFeature = _this.map.getFeaturesAtPixel(pixel);
        this.$refs.myMap.style.cursor = hit && hoverFeature.length >= 1 ? 'pointer' : '';
        var feature = _this.map.forEachFeatureAtPixel(evt.pixel,
            function (feature) {
              return feature
            })
        if (feature !== undefined) {
          this.restoreDeviceText()
          // 当移入图标时
          // 鼠标移入更改值
          feature.style_[1].text_.text_ = feature.name
          // 鼠标移入更改图标层级为优先级
          feature.style_[0].zIndex_ = 3
          feature.style_[1].zIndex_ = 3
          // 设置当前悬浮图层
          this.hoverFeature = feature
          this.hoverFeature.changed()

          // 停掉轮询定时器
          clearInterval(this.showDeviceTimer)
          this.showDeviceTimer = null;
        } else {
          // 当移出图标时
          if (this.hoverFeature) {
            // 鼠标移入更改图标层级为优先级
            this.hoverFeature.style_[0].zIndex_ = 1
            this.hoverFeature.style_[1].zIndex_ = 1
            // 重启轮询定时器
            if (!this.showDeviceTimer) {
              // 将设备tip标签值设置为空
              this.restoreDeviceText()
              this.pollShowDeviceText();
            }
          }
        }
      })
    },

八、给地图添加当前鼠标点击事件

    mapClick() {
      var _this = this;
      this.map.on('singleclick', evt => {
        var feature = _this.map.forEachFeatureAtPixel(evt.pixel,
            function (feature) {
              return feature
            })
        console.log("feature:", feature)
      })
    },

后记

到此完成!vue集成使用openlayers天地图的基本写法已经学会了吧?

Logo

前往低代码交流专区

更多推荐