基于Vue的Openlayers6自定义测量组件,直接拿来用

基于Vue的Openlayers6自定义测量组件,直接拿来用

  • 功能需求
  • 实现主要API
  • 源代码

在做WebGIS项目开发时,往往都有一个测量的基础功能,这个功能虽然不算太难,但是往往确花费不少的时间去完成,以前都是基于jQuery或原生js去做,现在基于Vue还得改,索性花上一点时间,模仿了其他平台的测量功能效果做一个组件,包含了 距离测量面积测量角度测量,下次可以直接拿来用。下面是效果图:

功能需求

测量组件需要拥有距离测量、面积测量、角度测量。距离测量需要标注出起点以及起点到后面每一个点的阶段距离,最后一个点标注测量的总距离;面积测量是在地图上绘制一个多边形,完成绘制后标注出多边形的面积;角度测量是绘制三个点,将第二个点为角的顶点。
在绘制图像时有操作提示,有动态测量的结果。

实现主要API

1.ol/interaction/Draw,用于绘制图形
2.ol/source/Vectorol/layer/Vector ,矢量数据源和图层,显示测量绘制的图形
3.ol/Overlay,显示测量标记
4.ol/sphere/getLengthol/sphere/getArea ,计算长度和面积
5.ol/style/*,设置样式

源代码

测量组件:

<template>
  <div class="measure-tool">
    <div class="ol-control"><button @click="measure('distence')"></button></div>
    <div class="ol-control"><button @click="measure('area')"></button></div>
    <div class="ol-control"><button @click="measure('angle')"></button></div>
  </div>
</template>

<script>
import { Draw } from "ol/interaction";
import { Vector as VectorSource } from "ol/source";
import { Vector as VectorLayer } from "ol/layer";
import Overlay from 'ol/Overlay';
import { Polygon, LineString } from 'ol/geom';
import Feature from 'ol/Feature';
import { unByKey } from 'ol/Observable'
import { getLength, getArea } from 'ol/sphere';
import Style from "ol/style/Style";
import Stroke from "ol/style/Stroke";
import Fill from "ol/style/Fill";
import Circle from "ol/style/Circle";
export default {
  props: ["map"],//地图组件传值
  data() {
    return {
      measureType: "diatence",
      draw: null,
      vectorLayer: null,
      tipDiv: null,
      pointermoveEvent: null, // 地图pointermove事件
      sketchFeature: null, // 绘制的要素
      geometryListener: null, // 要素几何change事件
      measureResult: "0" // 测量结果
    }
  },
  methods: {
    creatDraw(type) {
      let maxPoints = null;
      if (this.measureType == "angle") maxPoints = 3
      else maxPoints = null

      // 矢量图层源
      let vectorSource = new VectorSource({
        wrapX: false
      });
      // 矢量图层
      this.vectorLayer = new VectorLayer({
        source: vectorSource,
        style: new Style({
          fill: new Fill({
            color: 'rgba(252, 86, 49, 0.1)'
          }),
          stroke: new Stroke({
            color: '#fc5531',
            width: 3
          }),
          image: new Circle({
            radius: 0,
            fill: new Fill({
              color: '#fc5531'
            })
          })
        }),
        name: "测量图层"
      });
      this.map.addLayer(this.vectorLayer)
      this.draw = new Draw({
        source: vectorSource,
        type: type,
        maxPoints: maxPoints,
        style: new Style({
          fill: new Fill({
            color: 'rgba(252, 86, 49, 0.1)'
          }),
          stroke: new Stroke({
            color: '#fc5531',
            lineDash: [10, 10],
            width: 3
          }),
          image: new Circle({
            radius: 0,
            fill: new Fill({
              color: '#fc5531'
            })
          })
        }),
        // 绘制时点击处理事件
        condition: (evt) => {
          // 测距时添加点标注
          if (this.measureResult != "0" && !this.map.getOverlayById(this.measureResult) && this.measureType == "distence")
            this.creatMark(null, this.measureResult, this.measureResult).setPosition(evt.coordinate)
          return true
        }
      });
      this.map.addInteraction(this.draw);

      /**
       * 绘制开始事件
       */
      this.draw.on("drawstart", e => {
        this.sketchFeature = e.feature
        let proj = this.map.getView().getProjection()
        //******距离测量开始时*****//
        if (this.measureType == "distence") {
          this.creatMark(null, "起点", "start").setPosition(this.map.getCoordinateFromPixel(e.target.downPx_))
          this.tipDiv.innerHTML = "总长:0 m
单击确定地点,双击结束"
; this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => { this.measureResult = this.distenceFormat(getLength(evt.target, { "projection": proj, "radius": 6378137 })) this.tipDiv.innerHTML = "总长:" + this.measureResult + "
单击确定地点,双击结束"
; }) } //******面积测量开始时*****// else if (this.measureType == "area") { this.tipDiv.innerHTML = "面积:0 m2
继续单击确定地点"
; this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => { if (evt.target.getCoordinates()[0].length < 4) this.tipDiv.innerHTML = "面积:0m2
继续单击确定地点"
; else { this.measureResult = this.formatArea(getArea(evt.target, { "projection": proj, "radius": 6378137 })) this.tipDiv.innerHTML = "面积:" + this.measureResult + "
单击确定地点,双击结束"
; } }) } //******角度测量开始时*****// else if (this.measureType == "angle") { this.tipDiv.innerHTML = "继续单击确定顶点"; this.geometryListener = this.sketchFeature.getGeometry().on('change', (evt) => { if (evt.target.getCoordinates().length < 3) this.tipDiv.innerHTML = "继续单击确定顶点"; else { this.measureResult = this.formatAngle(evt.target) this.tipDiv.innerHTML = "角度:" + this.measureResult + "
继续单击结束"
; } }) } }); /** * 绘制开始事件 */ this.draw.on("drawend", e => { let closeBtn = document.createElement('span'); closeBtn.innerHTML = "×"; closeBtn.title = "清除测量" closeBtn.style = "width: 10px;height:10px;line-height: 12px;text-align: center;border-radius: 5px;display: inline-block;padding: 2px;color: rgb(255, 68, 0);border: 2px solid rgb(255, 68, 0);background-color: rgb(255, 255, 255);font-weight: 600;position: absolute;top: -25px;right: -2px;cursor: pointer;"; closeBtn.addEventListener('click', () => { this.clearMeasure() }) //******距离测量结束时*****// if (this.measureType == "distence") { this.creatMark(closeBtn, null, "close1").setPosition(e.feature.getGeometry().getLastCoordinate()); this.creatMark(null, "总长:" + this.measureResult + "", "length").setPosition(e.feature.getGeometry().getLastCoordinate()) this.map.removeOverlay(this.map.getOverlayById(this.measureResult)) } //******面积测量结束时*****// else if (this.measureType == "area") { this.creatMark(closeBtn, null, "close2").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates()); this.creatMark(null, "总面积:" + this.measureResult + "", "area").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates()) } //******角度测量结束时*****// else if (this.measureType == "angle") { this.creatMark(closeBtn, null, "close3").setPosition(e.feature.getGeometry().getCoordinates()[1]); this.creatMark(null, "角度:" + this.measureResult + "", "angle").setPosition(e.feature.getGeometry().getCoordinates()[1]) } // 停止测量 this.stopMeasure(); }); }, /** * 测量 */ measure(type) { if (this.draw != null) return false; // 防止在绘制过程再创建测量 this.measureType = type; if (this.vectorLayer != null) this.clearMeasure(); this.tipDiv = document.createElement('div'); this.tipDiv.innerHTML = '单击确定起点'; this.tipDiv.className = "tipDiv"; this.tipDiv.style = "width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;" let overlay = new Overlay({ element: this.tipDiv, autoPan: false, positioning: "bottom-center", id: "tipLay", stopEvent: false //停止事件传播到地图 }); this.map.addOverlay(overlay); this.pointermoveEvent = this.map.on("pointermove", evt => { overlay.setPosition(evt.coordinate) }) if (this.measureType == "distence" || this.measureType == "angle") { this.creatDraw("LineString") } else if (this.measureType == "area") { this.creatDraw("Polygon") } }, /** * 创建标记 */ creatMark(markDom, txt, idstr) { if (markDom == null) { markDom = document.createElement('div'); markDom.innerHTML = txt markDom.style = "width:auto;height:auto;padding:4px;border:1px solid #fc5531;font-size:12px;background-color:#fff;position:relative;top:60%;left:60%;font-weight:600;" } let overlay = new Overlay({ element: markDom, autoPan: false, positioning: "bottom-center", id: idstr, stopEvent: false }); this.map.addOverlay(overlay) return overlay; }, /** * 格式化距离结果输出 */ distenceFormat(length) { let output; if (length > 100) { output = (Math.round(length / 1000 * 100) / 100) + ' ' + 'km'; //换算成km单位 } else { output = (Math.round(length * 100) / 100) + ' ' + 'm'; //m为单位 } return output;//返回线的长度 }, /** * 格式化面积输出 */ formatArea(area) { let output; if (area > 10000) { output = (Math.round(area / 1000000 * 100) / 100) + ' ' + 'km2'; //换算成km单位 } else { output = (Math.round(area * 100) / 100) + ' ' + 'm2';//m为单位 } return output; //返回多边形的面积 }, /** * 计算角度输出 */ formatAngle(line) { var coordinates = line.getCoordinates(); var angle = '0°'; if (coordinates.length == 3) { const disa = getLength(new Feature({ geometry: new LineString([coordinates[0], coordinates[1]]) }).getGeometry(), { radius: 6378137, projection: this.map.getView().getProjection() }); const disb = getLength(new Feature({ geometry: new LineString([coordinates[1], coordinates[2]]) }).getGeometry(), { radius: 6378137, projection: this.map.getView().getProjection() }); const disc = getLength(new Feature({ geometry: new LineString([coordinates[0], coordinates[2]]) }).getGeometry(), { radius: 6378137, projection: this.map.getView().getProjection() }); var cos = (disa * disa + disb * disb - disc * disc) / (2 * disa * disb); // 计算cos值 angle = (Math.acos(cos) * 180) / Math.PI; // 角度值 angle = angle.toFixed(2); // 结果保留两位小数 } if (isNaN(angle)) return "0°" else return angle + "°"; // 返回角度 }, /** * 停止测量 */ stopMeasure() { this.tipDiv = null this.map.removeInteraction(this.draw); // 移除绘制组件 this.draw = null; this.map.removeOverlay(this.map.getOverlayById("tipLay")) // 移除动态提示框 }, /** * 清除测量 */ clearMeasure() { this.vectorLayer.getSource().clear() this.map.getOverlays().clear() //移除监听事件 unByKey(this.pointermoveEvent) // 清除鼠标在地图的pointermove事件 unByKey(this.geometryListener) // 清除绘制图像change事件 this.pointermoveEvent = null; this.geometryListener = null; this.measureResult = "0" } } } </script> <style scoped> .measure-tool { z-index: 1; position: absolute; left: 0.5em; top: 6em; width: 50px; height: 40px; } .measure-tool div:nth-child(2) { margin-top: 2em; } .measure-tool div:nth-child(3) { margin-top: 4em; } </style>

地图组件引入测量组件:

<template>
  <div id="map">
    <measureTool v-if="map" :map="map"></measureTool>
  </div>
</template>

这就是基于Vue实现的一个测量组件,有需要的朋友可以用用,有些不足的地方可以自己再优化一下,也欢迎指正,样式可以根据自己的需求修改,奥利给~

你可能感兴趣的:(OpenLayers,开源GIS,js,gis,map)