Vue+Openlayer中测距测面和绘制点线面组件-LjMeasureDraw4326和LjMeasureDraw3857

地理坐标系4326 

效果图:

 首先下载turf.js:

cnpm i -S @turf/turf

全局引入turf.js

//引入turf.js
import * as turf from '@turf/turf'
Vue.prototype.$turf = turf

使用:

<template>
  <div>
    <div id="map" style="width: 100vw; height: 100vh"></div>
    <div style="position: fixed; top: 20vh; left: 20vw">
      <el-button @click="isMeasure = !isMeasure">测量面板</el-button>
      <div v-if="isMeasure">
        <LjMeasureDraw4326 :map="map"></LjMeasureDraw4326>
      </div>
    </div>
  </div>
</template>
 
<script>
import "ol/ol.css";
import { Map, View } from "ol";
import { OSM } from "ol/source";
import { Tile as TileLayer } from "ol/layer";

import LjMeasureDraw4326 from "@/components/LjMeasureDraw4326/index.vue";

export default {
  components: { LjMeasureDraw4326 },
  data() {
    return {
      map: {},
      isMeasure: false,
    };
  },
  created() {},
  mounted() {
    this.initMap();
  },
  computed: {},
  methods: {
    initMap() {
      this.map = new Map({
        target: "map",

        layers: [
          new TileLayer({
            source: new OSM(),
          }),
        ],
        view: new View({
          projection: "EPSG:4326",
          center: [104.06335863843803, 30.659840919243138], //成都
          zoom: 18,
        }),
      });
    },
  },
};
</script>

组件  LjMeasureDraw4326/index.vue: 

<!-- LjMeasureDraw4326 -->
<template>
  <div>
    <el-radio-group v-model="isMeasure" @change="clearDrawLayer">
      <el-radio :label="true">测量</el-radio>
      <el-radio :label="false">绘制</el-radio>
    </el-radio-group>
    <div style="margin: 20px 0"></div>
    <LjButton size="mini">
      <select
        v-model="currentDrawFeature"
        style="
          background-color: rgb(66, 66, 66);
          color: white;
          padding: 2px 7px;
          outline: none;
        "
        @change="drawFeature()"
      >
        <option value="">请选择...</option>
        <option value="Point" label="画点" v-if="!isMeasure"></option>
        <option value="LineString">画线</option>
        <option value="Polygon">画面</option>
        <option value="Circle" v-if="!isMeasure">画圆</option>
      </select>
    </LjButton>

    <LjButton size="mini"
      ><el-checkbox v-model="enableFreeHand">手绘</el-checkbox></LjButton
    >
    <LjButton size="mini" @click="draw.removeLastPoint()">撤回</LjButton>
    <LjButton size="mini" @click="cancelDraw()">取消</LjButton>
    <LjButton size="mini" @click="clearDrawLayer()">清除</LjButton>
  </div>
</template>
<script>
import "ol/ol.css";
import { Vector as VectorLayer } from "ol/layer";
import { OSM, Vector as VectorSource } from "ol/source";
import Draw from "ol/interaction/Draw";
import { Fill, Stroke, Style, Text, Circle } from "ol/style";

import LjButton from "@/components/LjButton/index.vue";

export default {
  name: "LjMeasureDraw4326",
  components: { LjButton },
  props: {
    map: {
      type: Object,
      default: () => {},
      required: true,
    },
  },
  data() {
    return {
      draw_source: new VectorSource(),
      draw_vector: {},
      draw: {},
      currentDrawFeature: "", //当前正在绘制的要素类型
      enableFreeHand: false, //是否允许手绘
      isMeasure: true, //是否开启测量
    };
  },

  mounted() {
    this.addDrawLayer();
  },
  methods: {
    formatLength(length) {
      // length单位默认为km
      if (length >= 1) {
        return length.toFixed(2) + " km ";
      } else {
        return (length * 1000).toFixed(2) + " m ";
      }
    },
    formatArea(area) {
      // area默认为㎡
      if (area > 10000) {
        return (area / 1000000).toFixed(2) + " k㎡ ";
      } else {
        return area.toFixed(2) + " ㎡ ";
      }
    },
    //添加绘制点线面图层
    addDrawLayer() {
      this.draw_vector = new VectorLayer({
        source: this.draw_source,
        //绘制好后,在地图上呈现的样式
        style: (feature) => {
          if (this.isMeasure) {
            return new Style({
              fill: new Fill({
                color: "rgba(255, 255, 255, 0.2)",
              }),
              stroke: new Stroke({
                //边界样式
                color: "#ffcc33",
                width: 3,
              }),
              //点样式继承image
              image: new Circle({
                radius: 7,
                fill: new Fill({
                  color: "#ffcc33",
                }),
              }),
              text: new Text({
                // 位置
                textAlign: "center",
                // 基准线
                textBaseline: "middle",
                // 文字样式
                font: "bold 18px 微软雅黑",
                // 文本内容
                text: `${
                  feature.getGeometry().getType() == "Polygon"
                    ? this.formatArea(feature.get("area"))
                    : this.formatLength(feature.get("length"))
                }`,
                // 文字颜色
                fill: new Fill({
                  color: "black",
                }),
                // 文字背景
                stroke: new Stroke({ color: "yellow", width: 10 }),
              }),
            });
          } else {
            return new Style({
              fill: new Fill({
                color: "rgba(255, 255, 255, 0.2)",
              }),
              stroke: new Stroke({
                //边界样式
                color: "#ffcc33",
                width: 3,
              }),
              //点样式继承image
              image: new Circle({
                radius: 7,
                fill: new Fill({
                  color: "#ffcc33",
                }),
              }),
            });
          }
        },
      });
      this.map.addLayer(this.draw_vector);
    },
    //取消绘制
    cancelDraw() {
      this.map.removeInteraction(this.draw); //移除交互
      this.currentDrawFeature = ""; //取消选中要素!!
    },
    //清空绘制图层
    clearDrawLayer() {
      this.map.removeInteraction(this.draw); //移除交互
      this.draw_vector.getSource().clear(); //清除图层上的所有要素
      this.currentDrawFeature = ""; //取消选中要素!!
    },
    //绘制点线面
    drawFeature() {
      this.map.removeInteraction(this.draw); //移除交互

      if (!this.currentDrawFeature) return; //这里一定要判断

      this.draw = new Draw({
        source: this.draw_source,
        type: this.currentDrawFeature,
        //绘制时,在地图上呈现的样式
        style: new Style({
          fill: new Fill({
            color: "rgba(255, 255, 255, 0.2)",
          }),
          stroke: new Stroke({
            color: "#ffcc33",
            width: 2,
          }),
          image: new Circle({
            radius: 7,
            fill: new Fill({
              color: "#ffcc33",
            }),
          }),
        }),
        freehand: this.enableFreeHand, //手绘
      });
      if (this.isMeasure) {
        this.draw.on("drawend", (e) => {
          if (e.feature.getGeometry().getType() == "Polygon") {
            //面的折点坐标数组,如:[[[108,32],[109,33],[110,34],[108,32]]]为三角形
            let coordinates_polygon = e.feature.getGeometry().getCoordinates();
            // 面积计算,单位为平方米
            let area = this.$turf.area(this.$turf.polygon(coordinates_polygon));
            e.feature.set("area", area);
          }
          if (e.feature.getGeometry().getType() == "LineString") {
            let coordinates_lineString = e.feature
              .getGeometry()
              .getCoordinates();
            // 长度计算,单位为千米,单位还可以设置为degrees, radians, miles, or kilometers
            let length = this.$turf.length(
              this.$turf.lineString(coordinates_lineString),
              {
                units: "kilometers",
              }
            );
            e.feature.set("length", length);
          }
        });
      }
      this.map.addInteraction(this.draw);
    },
  },
  destroyed() {
    this.clearDrawLayer();
  },
  watch: {
    enableFreeHand: {
      handler() {
        this.drawFeature();
      },
    },
  },
};
</script>
<style lang="scss" scoped>
//单选框
::v-deep .el-radio__label {
  color: white;
}
//多选框
::v-deep .el-checkbox__label {
  color: white;
}
</style>

高级封装:

实现代码:

<template>
  <div>
    <div id="map" style="width: 100vw; height: 100vh"></div>
    <div
      style="
        position: fixed;
        top: 29vh;
        right: 30vw;
        z-index: 999;
        width: 50px;
        height: 50px;
      "
    >
      <!-- 测绘按钮 -->
      <div
        @click="showDrawPanel = !showDrawPanel"
        style="
          display: inline-block;
          width: 100%;
          height: 100%;
          display: flex;
          flex-flow: column nowrap;
          align-items: center;
          justify-content: space-around;
          background-color: #08305d;
          cursor: pointer;
          color: white;
        "
      >
        <i class="el-icon-edit" style="font-size: 24px"></i>
        <span style="font-size: 12px">测绘</span>
      </div>
      <!-- 绘制点线面面板 -->
      <LjMeasureDraw4326
        :map="map"
        v-if="showDrawPanel"
        style="
          position: absolute;
          top: 0px;
          left: -345px;
          z-index: 99999;
          width: 335px;
          height: 80px;
          background-color: rgba(0, 0, 0, 0.48);
          padding: 10px;
        "
      ></LjMeasureDraw4326>
    </div>
  </div>
</template>
 
<script>
import "ol/ol.css";
import { Map, View } from "ol";
import { OSM } from "ol/source";
import { Tile as TileLayer } from "ol/layer";
import LjMeasureDraw4326 from "@/components/LjMeasureDraw4326/index.vue";
export default {
  components: { LjMeasureDraw4326 },

  data() {
    return {
      map: {},
      showDrawPanel: false, //是否显示绘制点线面面板
    };
  },
  mounted() {
    this.initMap();
  },
  methods: {
    // 初始化地图
    initMap() {
      this.map = new Map({
        target: "map",
        layers: [
          new TileLayer({
            source: new OSM(),
          }),
        ],
        view: new View({
          projection: "EPSG:4326",
          center: [104.06334237328053, 30.65985125961902],
          zoom: 18,
        }),
      });
    },
  },
};
</script>

投影坐标系3857

使用:

<template>
  <div>
    <div id="map" style="width: 100vw; height: 100vh"></div>
    <div style="position: fixed; top: 20vh; left: 20vw">
      <el-button @click="isMeasure = !isMeasure">测量面板</el-button>
      <div v-if="isMeasure">
        <LjMeasureDraw3857 :map="map"></LjMeasureDraw3857>
      </div>
    </div>
  </div>
</template>
 
<script>
import "ol/ol.css";
import { Map, View } from "ol";
import { OSM } from "ol/source";
import { Tile as TileLayer } from "ol/layer";

import LjMeasureDraw3857 from "@/components/LjMeasureDraw3857/index.vue";

export default {
  components: { LjMeasureDraw3857 },
  data() {
    return {
      map: {},
      isMeasure: false,
    };
  },
  created() {},
  mounted() {
    this.initMap();
  },
  computed: {},
  methods: {
    initMap() {
      this.map = new Map({
        target: "map",

        layers: [
          new TileLayer({
            source: new OSM(),
          }),
        ],
        view: new View({
          projection: "EPSG:3857",//默认3857
          center: [11584282.38, 3588664.32], //成都
          zoom: 18,
        }),
      });
    },
  },
};
</script>

组件:LjMeasureDraw3857/index.vue

<template>
  <div>
    <el-radio-group v-model="isMeasure" @change="clearDrawLayer">
      <el-radio :label="true">测量</el-radio>
      <el-radio :label="false">绘制</el-radio>
    </el-radio-group>
    <div style="margin: 20px 0"></div>
    <el-button size="mini">
      <select
        v-model="currentDrawFeature"
        style="
          background-color: rgb(66, 66, 66);
          color: white;
          padding: 2px 7px;
          outline: none;
        "
        @change="drawFeature()"
      >
        <option value="">请选择...</option>
        <option value="Point" label="画点" v-if="!isMeasure"></option>
        <option value="LineString">画线</option>
        <option value="Polygon">画面</option>
        <option value="Circle" v-if="!isMeasure">画圆</option>
      </select>
    </el-button>

    <el-button size="mini"
      ><el-checkbox v-model="enableFreeHand">手绘</el-checkbox></el-button
    >
    <el-button size="mini" @click="draw.removeLastPoint()">撤回</el-button>
    <el-button size="mini" @click="cancelDraw()">取消</el-button>
    <el-button size="mini" @click="clearDrawLayer()">清除</el-button>
  </div>
</template>
<script>
import "ol/ol.css";
import { Vector as VectorLayer } from "ol/layer";
import { OSM, Vector as VectorSource } from "ol/source";
import Draw from "ol/interaction/Draw";
import { Fill, Stroke, Style, Text, Circle } from "ol/style";

import { getArea, getLength } from "ol/sphere";

export default {
  name: "LjMeasureDraw3857",
  components: {},
  props: {
    map: {
      type: Object,
      default: () => {},
      required: true,
    },
  },
  data() {
    return {
      draw_source: new VectorSource(),
      draw_vector: {},
      draw: {},
      currentDrawFeature: "", //当前正在绘制的要素类型
      enableFreeHand: false, //是否允许手绘
      isMeasure: true, //是否开启测量
    };
  },

  mounted() {
    this.addDrawLayer();
  },
  methods: {
    formatLength(length) {
      // length单位默认为m
      if (length < 999) {
        return length.toFixed(2) + " m ";
      } else {
        return (length / 1000).toFixed(2) + " km ";
      }
    },
    formatArea(area) {
      // area默认为㎡
      if (area > 10000) {
        return (area / 1000000).toFixed(2) + " k㎡ ";
      } else {
        return area.toFixed(2) + " ㎡ ";
      }
    },
    //添加绘制点线面图层
    addDrawLayer() {
      this.draw_vector = new VectorLayer({
        source: this.draw_source,
        //绘制好后,在地图上呈现的样式
        style: (feature) => {
          if (this.isMeasure) {
            return new Style({
              fill: new Fill({
                color: "rgba(255, 255, 255, 0.2)",
              }),
              stroke: new Stroke({
                //边界样式
                color: "#ffcc33",
                width: 3,
              }),
              //点样式继承image
              image: new Circle({
                radius: 7,
                fill: new Fill({
                  color: "#ffcc33",
                }),
              }),
              text: new Text({
                // 位置
                textAlign: "center",
                // 基准线
                textBaseline: "middle",
                // 文字样式
                font: "bold 18px 微软雅黑",
                // 文本内容
                text: `${
                  feature.getGeometry().getType() == "Polygon"
                    ? this.formatArea(feature.get("area"))
                    : this.formatLength(feature.get("length"))
                }`,
                // 文字颜色
                fill: new Fill({
                  color: "black",
                }),
                // 文字背景
                stroke: new Stroke({ color: "yellow", width: 10 }),
              }),
            });
          } else {
            return new Style({
              fill: new Fill({
                color: "rgba(255, 255, 255, 0.2)",
              }),
              stroke: new Stroke({
                //边界样式
                color: "#ffcc33",
                width: 3,
              }),
              //点样式继承image
              image: new Circle({
                radius: 7,
                fill: new Fill({
                  color: "#ffcc33",
                }),
              }),
            });
          }
        },
      });
      this.map.addLayer(this.draw_vector);
    },
    //取消绘制
    cancelDraw() {
      this.map.removeInteraction(this.draw); //移除交互
      this.currentDrawFeature = ""; //取消选中要素!!
    },
    //清空绘制图层
    clearDrawLayer() {
      this.map.removeInteraction(this.draw); //移除交互
      this.draw_vector.getSource().clear(); //清除图层上的所有要素
      this.currentDrawFeature = ""; //取消选中要素!!
    },
    //绘制点线面
    drawFeature() {
      this.map.removeInteraction(this.draw); //移除交互

      if (!this.currentDrawFeature) return; //这里一定要判断

      this.draw = new Draw({
        source: this.draw_source,
        type: this.currentDrawFeature,
        //绘制时,在地图上呈现的样式
        style: new Style({
          fill: new Fill({
            color: "rgba(255, 255, 255, 0.2)",
          }),
          stroke: new Stroke({
            color: "#ffcc33",
            width: 2,
          }),
          image: new Circle({
            radius: 7,
            fill: new Fill({
              color: "#ffcc33",
            }),
          }),
        }),
        freehand: this.enableFreeHand, //手绘
      });
      if (this.isMeasure) {
        this.draw.on("drawend", (e) => {
          if (e.feature.getGeometry().getType() == "Polygon") {
            let area = getArea(e.feature.getGeometry());

            e.feature.set("area", area);
          }
          if (e.feature.getGeometry().getType() == "LineString") {
            let length = getLength(e.feature.getGeometry());

            e.feature.set("length", length);
          }
        });
      }
      this.map.addInteraction(this.draw);
    },
  },
  destroyed() {
    this.clearDrawLayer();
  },
  watch: {
    enableFreeHand: {
      handler() {
        this.drawFeature();
      },
    },
  },
};
</script>
<style lang="scss" scoped>
//单选框
::v-deep .el-radio__label {
  color: white;
}
//多选框
::v-deep .el-checkbox__label {
  color: white;
}
</style>

封装测距测面

效果1:

MeasureTool2D.js

import "ol/ol.css";
import { Map, View, Feature } from "ol";
import { OSM, Vector as VectorSource, XYZ } from "ol/source";
import { Vector as VectorLayer, Tile as TileLayer } from "ol/layer";
import { Fill, Stroke, Style, Text, Circle } from "ol/style";
import Draw from "ol/interaction/Draw";
//引入turf.js
import * as turf from '@turf/turf'
import { getArea, getLength } from "ol/sphere"; //适用于墨卡托投影

export default class MeasureTool2D {
    constructor({ domId_map, proj = "wgs84" }) {
        this.domId_map = domId_map
        this.divDom = document.getElementById(domId_map)
        this.map = null
        this.source = new VectorSource()
        this.vector = null
        this.draw = {}
        this.proj = proj //wgs84,mct
    }
    initMap() {
        if (this.proj == 'wgs84') {
            this.map = new Map({
                target: this.domId_map,
                layers: [
                    new TileLayer({
                        source: new XYZ({
                            url:
                                "http://t3.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=5a257cd2df1b3311723bd77b0de14baf",
                        }),
                        visible: true,
                        // name: "OSM",
                    }),
                ],
                view: new View({
                    projection: "EPSG:4326",
                    center: [115, 39],
                    zoom: 4,
                }),
            })
        }
        if (this.proj == 'mct') {
            this.map = new Map({
                target: this.domId_map,
                layers: [
                    new TileLayer({
                        source: new XYZ({
                            url:
                                "http://t3.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=5a257cd2df1b3311723bd77b0de14baf",
                        }),
                        visible: true,
                        // name: "OSM",
                    }),
                ],
                view: new View({
                    projection: "EPSG:3857",
                    center: [11584282.38, 3588664.32],
                    zoom: 4,
                }),
            })
        }
    }
    //添加绘制图层
    addDrawLayer() {
        if (this.vector) this.map.removeLayer(this.vector)

        this.vector = new VectorLayer({
            source: this.source,
            //绘制好后,在地图上呈现的样式
            style: (feature) => {
                return new Style({
                    fill: new Fill({
                        color: "rgba(255, 255, 255, 0.2)",
                    }),
                    stroke: new Stroke({
                        //边界样式
                        color: "#ffcc33",
                        width: 3,
                    }),
                    //点样式继承image
                    image: new Circle({
                        radius: 7,
                        fill: new Fill({
                            color: "#ffcc33",
                        }),
                    }),
                    text: new Text({
                        // 位置
                        textAlign: "center",
                        // 基准线
                        textBaseline: "middle",
                        // 文字样式
                        font: "bold 18px 微软雅黑",
                        // 文本内容
                        text: `${feature.getGeometry().getType() == "Polygon"
                            ? this.formatArea(feature.get("area"))
                            : this.formatLength(feature.get("length"))
                            }`,
                        // 文字颜色
                        fill: new Fill({
                            color: "black",
                        }),
                        // 文字背景
                        stroke: new Stroke({ color: "yellow", width: 10 }),
                    }),
                });
            },
        });
        this.map.addLayer(this.vector);
    }
    formatLength(length) {
        // length单位默认为km
        if (length >= 1) {
            return length.toFixed(2) + " km ";
        } else {
            return (length * 1000).toFixed(2) + " m ";
        }
    }
    formatArea(area) {
        // area默认为㎡
        if (area > 10000) {
            return (area / 1000000).toFixed(2) + " k㎡ ";
        } else {
            return area.toFixed(2) + " ㎡ ";
        }
    }
    // 测距
    measureDistance() {
        if (this.draw) this.map.removeInteraction(this.draw); //移除绘制交互
        this.divDom.style.cursor = 'crosshair'
        this.draw = new Draw({
            source: this.source,
            type: 'LineString',
            //绘制时,在地图上呈现的样式
            style: new Style({
                fill: new Fill({
                    color: "rgba(255, 255, 255, 0.2)",
                }),
                stroke: new Stroke({
                    color: "#ffcc33",
                    width: 2,
                }),
                image: new Circle({
                    radius: 3,
                    fill: new Fill({
                        color: "#ffcc33",
                    }),
                }),
            }),
        });
        this.map.addInteraction(this.draw);

        this.draw.on("drawend", (e) => {
            let coordinates_lineString = e.feature
                .getGeometry()
                .getCoordinates();
            // 长度计算,单位为千米,单位还可以设置为degrees, radians, miles, or kilometers
            let length = ''
            if (this.proj == 'wgs84') {
                length = turf.length(
                    turf.lineString(coordinates_lineString),
                    {
                        units: "kilometers",
                    }
                );
            }
            if (this.proj == 'mct') {
                length = getLength(e.feature.getGeometry()) * 0.001
            }

            e.feature.set("length", length);
        });
    }

    // 测面
    measureArea() {
        if (this.draw) this.map.removeInteraction(this.draw); //移除绘制交互
        this.divDom.style.cursor = 'crosshair'

        this.draw = new Draw({
            source: this.source,
            type: 'Polygon',
            //绘制时,在地图上呈现的样式
            style: new Style({
                fill: new Fill({
                    color: "rgba(255, 255, 255, 0.2)",
                }),
                stroke: new Stroke({
                    color: "#ffcc33",
                    width: 2,
                }),
                image: new Circle({
                    radius: 2,
                    fill: new Fill({
                        color: "#ffcc33",
                    }),
                }),
            }),
        });
        this.map.addInteraction(this.draw);

        this.draw.on("drawend", (e) => {
            //面的折点坐标数组,如:[[[108,32],[109,33],[110,34],[108,32]]]为三角形
            let coordinates_polygon = e.feature.getGeometry().getCoordinates();
            // 面积计算,单位为平方米
            let area = ""
            if (this.proj == 'wgs84') {
                area = turf.area(turf.polygon(coordinates_polygon));
            }
            if (this.proj == 'mct') {
                area = getArea(e.feature.getGeometry());
            }

            e.feature.set("area", area);
        });
    }

    // 取消测量
    cancelMeasure() {
        if (this.draw) this.map.removeInteraction(this.draw); //移除绘制交互
        this.divDom.style.cursor = 'default'

    }
    // 清除
    clearMeasure() {
        this.map.removeInteraction(this.draw); //移除交互
        this.source.clear(); //清除图层上的所有要素
        this.divDom.style.cursor = 'default'

    }
}

 测试:

<template>
    <div class="main">
        <div style="position: absolute;z-index:99;left:30%;top:20px">
            <div>
                <el-button @click="state.measure.measureDistance()">测距</el-button>
                <el-button @click="state.measure.measureArea()">测面</el-button>
                <el-button @click="state.measure.cancelMeasure()">取消测量</el-button>
                <el-button @click="state.measure.clearMeasure()">清除</el-button>
            </div>
        </div>

        <div id="olContainer" style="height: 100vh; width: 100vw"></div>
    </div>

</template>
  
<script setup>
import { reactive } from 'vue';
import MeasureTool2D from './MeasureTool2D'

const state = reactive({
    measure: null
})
onMounted(() => {
    let measure = new MeasureTool2D({ domId_map: 'olContainer', proj: "wgs84" })
    measure.initMap()
    measure.addDrawLayer()
    state.measure = measure
})
</script>
<style lang="scss" scoped>
.main {
    position: relative;
    height: 100vh;
    width: 100vw;

    #olContainer {
        position: absolute;
        height: 100vh;
        width: 100vw;
    }

}
</style>

效果2:

MeasureTool2D.js:

import { Map, View } from "ol";
import Feature from "ol/Feature";

import Draw from 'ol/interaction/Draw';
import Overlay from 'ol/Overlay';
import { Circle as CircleStyle, Fill, Stroke, Style } from 'ol/style';
import { LineString, Polygon } from 'ol/geom';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer';
import { getArea, getLength } from 'ol/sphere';
import { unByKey } from 'ol/Observable';


/**
 * 二维测量工具类
 */
export default class MeasureTool2D {
    constructor({ map }) {
        this.map = map
        this.source = new VectorSource();
        this.vector = new VectorLayer({
            source: this.source,
            style: {
                'fill-color': 'rgba(255, 255, 255, 0.2)',
                'stroke-color': '#ffff00',
                'stroke-width': 2,
                'circle-radius': 7,
                'circle-fill-color': '#ffff00',
            },
        });
        this.map.addLayer(this.vector)

        this.currentDrawFeature = null;//当前绘制要素
        this.helpTooltipElement = null;
        this.helpTooltip;
        this.measureTooltipElement;
        this.measureTooltip;
        this.continuePolygonMsg = '单击绘制多边形,双击结束';
        this.continueLineMsg = '单击绘制画线,双击结束';
        this.vector.setZIndex(999)
        this.draw;
        this.overlayGroups = []
        this.typeOptions = {
            'area': 'Polygon',
            'length': 'LineString'
        }
        this.geometry = null;

    }
    pointerMoveHandler(evt) {
        if (evt.dragging) {
            return;
        }
        let helpMsg = '单击开始绘制';

        if (this.currentDrawFeature) {
            const geom = this.currentDrawFeature.getGeometry();
            if (geom instanceof Polygon) {
                helpMsg = this.continuePolygonMsg;
            } else if (geom instanceof LineString) {
                helpMsg = this.continueLineMsg;
            }
        }
        if (this.helpTooltipElement) {
            this.helpTooltipElement.innerHTML = helpMsg;
            this.helpTooltip.setPosition(evt.coordinate);

            this.helpTooltipElement.classList.remove('hidden');
        }

    }
    formatLength(line) {
        const length = getLength(line);
        let output;
        if (length > 100) {
            output = Math.round((length / 1000) * 100) / 100 + ' ' + '公里';
        } else {
            output = Math.round(length * 100) / 100 + ' ' + '米';
        }
        return output;
    }
    formatArea(polygon) {
        const area = getArea(polygon);
        let output;
        if (area > 10000) {
            output = Math.round((area / 1000000) * 100) / 100 + ' ' + '平方公里';
        } else {
            output = Math.round(area * 100) / 100 + ' ' + '平方米';
        }
        return output;
    }
    addInteraction(typeSelect) {

        if (this.draw) this.destory()
        const type = this.typeOptions[typeSelect];
        if (!type) {
            return;
        }
        this.draw = new Draw({
            source: this.source,
            type: type,
            style: new Style({
                fill: new Fill({
                    color: 'rgba(255, 255, 255, 0.2)',
                }),
                stroke: new Stroke({
                    color: 'rgba(0, 142, 255, 0.5)',
                    lineDash: [10, 10],
                    width: 2,
                }),
                image: new CircleStyle({
                    radius: 5,
                    stroke: new Stroke({
                        color: 'rgba(0, 142, 255, 0.7)',
                    }),
                    fill: new Fill({
                        color: 'rgba(255, 255, 255, 0.2)',
                    }),
                }),
            }),
        });
        this.map.addInteraction(this.draw);


        this.createMeasureTooltip();
        this.createHelpTooltip();

        this.map.on('pointermove', (evt) => this.pointerMoveHandler(evt));

        this.map.getViewport().addEventListener('mouseout', () => {
            this.helpTooltipElement.classList.add('hidden');
        });

        let listener;
        this.draw.on('drawstart', (evt) => {

            this.currentDrawFeature = evt.feature;

            evt.feature.type = typeSelect

            let tooltipCoord = evt.coordinate;

            listener = this.currentDrawFeature.getGeometry().on('change', (evt) => {
                const geom = evt.target;
                const p = geom.clone();
                const circleIn4326 = p
                    .transform(
                        this.map.getView().getProjection(),
                        "EPSG:3857"
                    );
                let output;
                if (geom instanceof Polygon) {
                    output = this.formatArea(circleIn4326);
                    tooltipCoord = geom.getInteriorPoint().getCoordinates();
                } else if (geom instanceof LineString) {
                    output = this.formatLength(circleIn4326);
                    tooltipCoord = geom.getLastCoordinate();
                }
                this.measureTooltipElement.innerHTML = output;
                this.measureTooltip.setPosition(tooltipCoord);
                this.geometry = geom
            });
        });
        this.map.on('singleclick', () => {
            if (typeSelect !== "length") return;
            const element = document.createElement('div');
            element.className = 'ol-tooltip ol-tooltip-static';
            if (this.geometry.getCoordinates() && this.geometry.getCoordinates().length < 3) element.innerHTML = '开始'
            else element.innerHTML = this.measureTooltipElement.innerHTML

            const lastTwo = this.geometry.getCoordinates()[this.geometry.getCoordinates().length - 2]
            const tooltip = new Overlay({
                element: element,
                offset: [0, -15],
                positioning: 'bottom-center',
                stopEvent: false,
                insertFirst: false,
            });
            tooltip.setPosition([lastTwo[0], lastTwo[1]])
            this.map.addOverlay(tooltip);
            this.overlayGroups.push(tooltip)
        })
        this.draw.on('drawend', (evt) => {
            this.currentDrawFeature = null;
            this.measureTooltipElement = null;
            this.createMeasureTooltip();
            unByKey(listener);

            setTimeout(() => {
                this.destory()
            }, 300);

        });
    }
    createHelpTooltip() {
        if (this.helpTooltipElement) {
            this.helpTooltipElement.parentNode.removeChild(this.helpTooltipElement);
        }
        this.helpTooltipElement = document.createElement('div');
        this.helpTooltipElement.className = 'ol-tooltip hidden';
        this.helpTooltip = new Overlay({
            element: this.helpTooltipElement,
            offset: [15, 0],
            positioning: 'center-left',
        });
        this.map.addOverlay(this.helpTooltip);
    }
    createMeasureTooltip() {
        if (this.measureTooltipElement) {
            this.measureTooltipElement.parentNode.removeChild(this.measureTooltipElement);
        }
        this.measureTooltipElement = document.createElement('div');
        this.measureTooltipElement.className = 'ol-tooltip ol-tooltip-measure';
        this.measureTooltip = new Overlay({
            element: this.measureTooltipElement,
            offset: [0, -15],
            positioning: 'bottom-center',
            stopEvent: false,
            insertFirst: false,
        });
        this.map.addOverlay(this.measureTooltip);
        this.overlayGroups.push(this.measureTooltip)
    }
    clearMeasure() {
        if (this.overlayGroups.length > 1) {
            this.overlayGroups.forEach((el) => {
                this.map.removeOverlay(el);
            })
        }
        this.vector.getSource().clear()
        this.overlayGroups = []
    }
    destory() {

        this.map.un('pointermove', this.pointerMoveHandler);

        this.map.removeOverlay(this.helpTooltip);
        this.map.removeOverlay(this.measureTooltip);
        this.map.removeInteraction(this.draw);
        this.draw = null
    }
}

测试:

<template>
    <div class="main">
        <div style="position: absolute;z-index:99;left:30%;top:20px">
            <div>
                <el-button @click="state.measure.addInteraction('length')">测距</el-button>
                <el-button @click="state.measure.addInteraction('area')">测面</el-button>
                <!-- <el-button @click="state.measure.cancelMeasure()">取消测量</el-button> -->
                <el-button @click="state.measure.clearMeasure()">清除</el-button>
            </div>
        </div>

        <div id="olContainer" style="height: 100vh; width: 100vw"></div>
    </div>

</template>
  
<script setup>
import { reactive } from 'vue';
import MeasureTool2D from './MeasureTool2D'
import { Map, View } from "ol";
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Vector as VectorLayer, Tile as TileLayer } from 'ol/layer';


const state = reactive({
    measure: null
})

onMounted(() => {
    const map = new Map({
        target: "olContainer",
        layers: [
            new TileLayer({
                source: new XYZ({
                    url:
                        "http://t3.tianditu.com/DataServer?T=img_w&x={x}&y={y}&l={z}&tk=5a257cd2df1b3311723bd77b0de14baf",
                }),
                visible: true,
            }),
        ],
        view: new View({
            projection: "EPSG:4326",
            center: [115, 39],
            zoom: 4,
        }),
    })
    state.measure = new MeasureTool2D({ map })
})
</script>
<style lang="scss" scoped>
.main {
    position: relative;
    height: 100vh;
    width: 100vw;

    #olContainer {
        position: absolute;
        height: 100vh;
        width: 100vw;
    }

}
</style>

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值