openlayers+vue3 测距、面、角组件

  • 组件代码
<template>
<div></div>
</template>
<script lang='ts' setup>
import { ref, reactive } from 'vue'

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";

// let selectList = [
//   {id:'distence',title:'测距'},
//   {id:'area',title:'测面'},
//   {id:'angle',title:'测角'},
// ]

interface Props {
  map?:any
}
const {map} = defineProps<Props>()

let measureType:string ='diatence' //类型
let draw:any = ref(null)
let vectorLayer:any = ref(null)
let tipDiv:any = ref(null)
let pointermoveEvent:any = null // 地图pointermove事件
let sketchFeature:any = null // 绘制的要素
let geometryListener:any = null // 要素几何change事件
let measureResult:any = "0" // 测量结果


const creatDraw = (type:string) => {
  let maxPoints:any = null;
  if (measureType == "angle") maxPoints = 3
  else maxPoints = null

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

  /**
   * 绘制开始事件
   */
   draw.value.on("drawstart", (e:any) => {
    sketchFeature = e.feature
    let proj = map.getView().getProjection()
    //******距离测量开始时*****//
    if (measureType == "distence") {
      creatMark(null, "起点", "start").setPosition(map.getCoordinateFromPixel(e.target.downPx_))
      tipDiv.value.innerHTML = "总长:0 m</br>单击确定地点,双击结束";
      geometryListener = sketchFeature.getGeometry().on('change', (evt:any) => {
        measureResult = distenceFormat(getLength(evt.target, { "projection": proj, "radius": 6378137 }))
        tipDiv.value.innerHTML = "总长:" + measureResult + "</br>单击确定地点,双击结束";
      })
    }
    //******面积测量开始时*****//
    else if (measureType == "area") {
      tipDiv.value.innerHTML = "面积:0 m<sup>2</sup></br>继续单击确定地点";
      geometryListener = sketchFeature.getGeometry().on('change', (evt:any) => {
        if (evt.target.getCoordinates()[0].length < 4) tipDiv.value.innerHTML = "面积:0m<sup>2</sup></br>继续单击确定地点";
        else {
          measureResult = formatArea(getArea(evt.target, { "projection": proj, "radius": 6378137 }))
          tipDiv.value.innerHTML = "面积:" + measureResult + "</br>单击确定地点,双击结束";
        }
      })
    }
    //******角度测量开始时*****//
    else if (measureType == "angle") {
      tipDiv.value.innerHTML = "继续单击确定顶点";
      geometryListener = sketchFeature.getGeometry().on('change', (evt:any) => {
        if (evt.target.getCoordinates().length < 3) tipDiv.value.innerHTML = "继续单击确定顶点";
        else {
          measureResult = formatAngle(evt.target)
          tipDiv.value.innerHTML = "角度:" + measureResult + "</br>继续单击结束";
        }
      })
    }
  });

  /**
   * 绘制开始事件
   */
   draw.value.on("drawend", (e:any) => {
    let closeBtn = document.createElement('span');
    closeBtn.innerHTML = "×";
    closeBtn.title = "清除测量"
    closeBtn.style = `
      width: 20px;
      height:20px;
      color:#000;
      line-height: 12px;
      text-align: center;
      border-radius: 50%;
      display: inline-block;
      padding: 2px;
      color: rgb(46, 198, 255);
      border: 2px solid rgb(46, 198, 255);
      background-color: rgb(255, 255, 255);
      font-weight: 600;
      position: absolute;
      top: -36px;
      right: -17px;
      font-size: 15px;
      cursor: pointer;`;
    closeBtn.addEventListener('click', () => {
      clearMeasure()
    })
    //******距离测量结束时*****//
    if (measureType == "distence") {
      creatMark(closeBtn, null, "close1").setPosition(e.feature.getGeometry().getLastCoordinate());
      creatMark(null, "总长:" + measureResult + "", "length").setPosition(e.feature.getGeometry().getLastCoordinate())
      map.removeOverlay(map.getOverlayById(measureResult))
    }
    //******面积测量结束时*****//
    else if (measureType == "area") {
      creatMark(closeBtn, null, "close2").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates());
      creatMark(null, "总面积:" + measureResult + "", "area").setPosition(e.feature.getGeometry().getInteriorPoint().getCoordinates())
    }
    //******角度测量结束时*****//
    else if (measureType == "angle") {
      creatMark(closeBtn, null, "close3").setPosition(e.feature.getGeometry().getCoordinates()[1]);
      creatMark(null, "角度:" + measureResult + "", "angle").setPosition(e.feature.getGeometry().getCoordinates()[1])
    }
    // 停止测量
    stopMeasure();
  });
}
// 测量
const measure = (type:string) => {
  if (draw.value != null) return false; // 防止在绘制过程再创建测量
  measureType = type;
  if (vectorLayer.value != null) clearMeasure();
  tipDiv.value = document.createElement('div');
  tipDiv.value.innerHTML = '单击确定起点';
  tipDiv.value.className = "tipDiv.value";
  tipDiv.value.style = `
    width:auto;
    height:auto;
    color:#000;
    padding:4px;
    border:1px solid #2ec6ff;
    font-size:12px;
    background-color:#fff;
    position:relative;
    top:60%;
    left:60%;
    font-weight:600;`

  let overlay = new Overlay({
    element: tipDiv.value,
    autoPan: false,
    positioning: "bottom-center",
    id: "tipLay",
    stopEvent: false //停止事件传播到地图
  });
  map.addOverlay(overlay);

  pointermoveEvent = map.on("pointermove", (evt:any) => {
    overlay.setPosition(evt.coordinate)
  })
  if (measureType == "distence" || measureType == "angle") {
    creatDraw("LineString")
  }
  else if (measureType == "area") {
    creatDraw("Polygon")
  }
}
// 创建标记
const creatMark = (markDom:any, txt:any, idstr:any) => {
  if (markDom == null) {
    markDom = document.createElement('div');
    markDom.innerHTML = txt
    markDom.style = `
      width:auto;
      height:auto;
      color:#000;
      padding:4px;
      border:1px solid #2ec6ff;
      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
  });
  map.addOverlay(overlay)
  return overlay;
}
// 格式化距离结果输出
const distenceFormat = (length:number) => {
  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;//返回线的长度
}
// 格式化面积输出
const formatArea = (area:number) => {
  let output;
  if (area > 10000) {
    output = (Math.round(area / 1000000 * 100) / 100) + ' ' + 'km<sup>2</sup>'; //换算成km单位
  } else {
    output = (Math.round(area * 100) / 100) + ' ' + 'm<sup>2</sup>';//m为单位
  }
  return output; //返回多边形的面积
}
// 计算角度输出
const formatAngle = (line:any) => {
  var coordinates = line.getCoordinates();
  var angle = '0°';
  if (coordinates.length == 3) {
    const disa:any = getLength(new Feature({
      geometry: new LineString([coordinates[0], coordinates[1]])
    }).getGeometry(), {
      radius: 6378137,
      projection: map.getView().getProjection()
    });

    const disb = getLength(new Feature({
      geometry: new LineString([coordinates[1], coordinates[2]])
    }).getGeometry(), {
      radius: 6378137,
      projection: map.getView().getProjection()
    });

    const disc = getLength(new Feature({
      geometry: new LineString([coordinates[0], coordinates[2]])
    }).getGeometry(), {
      radius: 6378137,
      projection: 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 + "°"; // 返回角度
}
// 停止测量
const stopMeasure = () => {
  tipDiv.value = null
  map.removeInteraction(draw.value); // 移除绘制组件
  draw.value = null;
  map.removeOverlay(map.getOverlayById("tipLay")) // 移除动态提示框
}
// 清除测量
const clearMeasure = () => {
  vectorLayer.value.getSource().clear()
  map.getOverlays().clear()
  //移除监听事件
  unByKey(pointermoveEvent) // 清除鼠标在地图的pointermove事件
  unByKey(geometryListener) // 清除绘制图像change事件
  pointermoveEvent = null;
  geometryListener = null;
  measureResult = "0"
}
// 重置
const resetMeasure = () => {
  if (draw.value != null) stopMeasure();
  if (vectorLayer.value != null) clearMeasure();
}

defineExpose({
  measure,
  clearMeasure,
  resetMeasure
})

</script>
<style scoped lang='scss'>

</style>
  • 效果


    计算面积
距离测量
角度测量
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容