Skip to content

测量

TIP

单击开始绘图。左下角可以选择几何体的类型和是否使用测地线的措施。

Vue 代码如下:

点我查看代码
vue
<template>
  <div id="map">
    <div id="menu">
      几何体类型
      <el-select
        v-model="state.typeSelect"
        placeholder="Select"
        size="small"
        @change="handleChange"
        class="select"
      >
        <el-option
          v-for="item in state.options"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        />
      </el-select>
      <el-checkbox
        v-model="state.checked"
        label="使用测地线的措施"
        size="large"
      />
    </div>
  </div>
</template>

<script lang="ts" setup>
import { onMounted, reactive, onBeforeUnmount } from "vue";
import { Map, View, Overlay, Feature } from "ol";
import { Coordinate } from "ol/coordinate";
import { Tile as TileLayer } from "ol/layer";
import { Vector } from "ol/source";
import { Polygon, LineString, Geometry } from "ol/geom";
import { unByKey } from "ol/Observable";
import { MAPURL, ATTRIBUTIONS } from "../../../constants";
import { Draw } from "ol/interaction";
import { DrawEvent } from "ol/interaction/Draw";
import { EventsKey } from "ol/events";
import XYZ from "ol/source/XYZ";

import {
  createHelpTooltip,
  createMeasureTooltip,
  formatLength,
  formatArea,
  drawGeometricFigure,
  createVector,
} from "./index";

interface State {
  checked: boolean;
  typeSelect: string;
  options: {
    label: string;
    value: string;
  }[];
  map: null | Map;
  sketch: null | Feature;
  helpTooltipElement: null | HTMLElement;
  helpTooltip: null | Overlay;
  measureTooltipElement: null | HTMLElement;
  measureTooltip: null | Overlay;
  draw: null | Draw;
}
const raster = new TileLayer({
  source: new XYZ({
    attributions: ATTRIBUTIONS,
    url: MAPURL,
    maxZoom: 20,
  }),
});

/**
 *  当用户正在绘制多边形时的提示信息文本
 * @type {string}
 */
const continuePolygonMsg = "单击继续绘制多边形";
/**
 * 当用户正在绘制线时的提示信息文本
 * @type {string}
 */
const source = new Vector();
const continueLineMsg = "单击继续绘制直线";
const state: State = reactive({
  checked: false,
  typeSelect: "length",
  options: [
    {
      label: "长度",
      value: "length",
    },
    {
      label: "面积",
      value: "area",
    },
  ],
  map: null,
  sketch: null, // 当前绘制的要素
  helpTooltipElement: null, // 帮助提示框对象
  helpTooltip: null, // 帮助提示框显示的信息
  measureTooltipElement: null, // 测量工具提示框对象
  measureTooltip: null, // 测量工具中显示的测量值
  draw: null,
});
//初始化map
const initMap = () => {
  state.map = new Map({
    target: "map",
    //地图容器中加载的图层
    layers: [
      //加载瓦片图层数据
      raster,
    ],
    view: new View({
      projection: "EPSG:4326", // 坐标系,有EPSG:4326和EPSG:3 857
      center: [0, 0],
      //地图初始显示级别
      zoom: 5,
    }),
  });
};

// 加载测量的绘制矢量层
const loadVector = () => {
  if (state.map === null) return;
  // 加载测量的绘制矢量层
  const vector = createVector(source);
  state.map.addLayer(vector);
};
/**
 * 鼠标移动事件处理函数
 * @param {ol.MapBrowserEvent} evt
 */
const pointerMoveHandler = (evt) => {
  if (evt.dragging) {
    return;
  }
  /** @type {string} */
  let helpMsg = "单击开始绘图"; //当前默认提示信息
  //判断绘制几何类型设置相应的帮助提示信息
  if (state.sketch) {
    const getGeometry = state.sketch.getGeometry();
    if (getGeometry instanceof Polygon) {
      helpMsg = continuePolygonMsg; //绘制多边形时提示相应内容
    } else if (getGeometry instanceof LineString) {
      helpMsg = continueLineMsg; //绘制线时提示相应内容
    }
  }
  if (state.helpTooltipElement === null || state.helpTooltip === null) return;
  state.helpTooltipElement.innerHTML = helpMsg; //将提示信息设置到对话框中显示
  state.helpTooltip.setPosition(evt.coordinate); //设置帮助提示框的位置
  state.helpTooltipElement.classList.remove("hidden");
};

/**
 * 加载交互绘制控件函数
 */
const addInteraction = () => {
  if (state.map === null) return;

  const type = state.typeSelect === "area" ? "Polygon" : "LineString";
  state.draw = drawGeometricFigure({ source, type });
  state.map.addInteraction(state.draw);

  //创建测量工具提示框
  [state.measureTooltipElement, state.measureTooltip] = createMeasureTooltip({
    measureTooltipElement: state.measureTooltipElement,
    map: state.map,
  });

  //创建帮助提示框
  [state.helpTooltipElement, state.helpTooltip] = createHelpTooltip({
    helpTooltipElement: state.helpTooltipElement,
    map: state.map,
  });

  let listener: EventsKey;

  //绑定交互绘制工具开始绘制的事件
  state.draw.on("drawstart", function (evt: DrawEvent) {
    state.sketch = evt.feature; //绘制的要素

    let tooltipCoord: Coordinate; // 绘制的坐标

    //绑定change事件,根据绘制几何类型得到测量长度值或面积值,并将其设置到测量工具提示框中显示
    const getGeometry = state.sketch.getGeometry();

    if (getGeometry) {
      listener = getGeometry.on("change", function (evt) {
        if (state.map === null) return;
        const geom: Geometry = evt.target; //绘制几何要素
        let output: string = "";

        if (geom instanceof Polygon) {
          output = formatArea({
            checked: state.checked,
            polygon: geom,
            map: state.map,
          }); //面积值
          tooltipCoord = geom.getInteriorPoint().getCoordinates(); //坐标
        } else if (geom instanceof LineString) {
          output = formatLength({
            checked: state.checked,
            line: geom,
            map: state.map,
          }); //长度值
          tooltipCoord = geom.getLastCoordinate(); //坐标
        }

        if (state.measureTooltipElement) {
          state.measureTooltipElement.innerHTML = output; //将测量值设置到测量工具提示框中显示
        }

        if (state.measureTooltip) {
          state.measureTooltip.setPosition(tooltipCoord); //设置测量工具提示框的显示位置
        }
      });
    }
  });

  //绑定交互绘制工具结束绘制的事件
  state.draw.on("drawend", function () {
    if (
      state.measureTooltipElement !== null &&
      state.measureTooltip !== null &&
      state.map !== null
    ) {
      state.measureTooltipElement.className = "tooltip tooltip-static"; //设置测量提示框的样式
      state.measureTooltip.setOffset([0, -7]);
      // 未设置的草图
      state.sketch = null; //置空当前绘制的要素对象
      // 取消设置工具提示,以便创建新的工具提示
      state.measureTooltipElement = null; //置空测量工具提示框对象
      //重新创建一个测试工具提示框显示结果
      [state.measureTooltipElement, state.measureTooltip] =
        createMeasureTooltip({
          measureTooltipElement: state.measureTooltipElement,
          map: state.map,
        });
      unByKey(listener);
    }
  });
};

// 让用户切换选择测量类型(长度/面积)
const handleChange = () => {
  if (state.map === null || state.draw === null) return;
  state.map.removeInteraction(state.draw); //移除绘制图形
  addInteraction(); //添加绘图进行测量
};

onMounted(() => {
  initMap();
  loadVector();
  if (state.map) {
    state.map.on("pointermove", pointerMoveHandler); //地图容器绑定鼠标移动事件,动态显示帮助提示框内容
    //地图绑定鼠标移出事件,鼠标移出时为帮助提示框设置隐藏样式
    state.map.getViewport().addEventListener("mouseout", () => {
      if (state.helpTooltipElement) {
        state.helpTooltipElement.classList.add("hidden");
      }
    });
  }
  addInteraction(); //调用加载绘制交互控件方法,添加绘图进行测量
});

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

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
#map {
  position: relative;
  width: 100%;
  height: 650px;
}

#menu {
  width: 450px;
  position: absolute;
  left: 20px;
  bottom: 20px;
  color: #fff;
  font-size: 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
  z-index: 1;
}

/**
    * 提示框的样式信息
    */
:deep(.tooltip) {
  position: relative;
  background: rgba(0, 0, 0, 0.5);
  border-radius: 4px;
  color: white;
  padding: 4px 8px;
  opacity: 0.7;
  white-space: nowrap;
}

:deep(.tooltip-measure) {
  opacity: 1;
  font-weight: bold;
}

:deep(.tooltip-static) {
  background-color: #ffcc33;
  color: black;
  border: 1px solid white;
}

:deep(.tooltip-measure:before),
:deep(.tooltip-static:before) {
  border-top: 6px solid rgba(0, 0, 0, 0.5);
  border-right: 6px solid transparent;
  border-left: 6px solid transparent;
  content: "";
  position: absolute;
  bottom: -6px;
  margin-left: -7px;
  left: 50%;
}

:deep(.tooltip-static:before) {
  border-top-color: #ffcc33;
}
</style>

index.ts 代码如下:

点我查看代码
ts
import { Overlay, Map } from "ol";
import { LineString, Polygon } from 'ol/geom'
import VectorSource from 'ol/source/Vector'
import { Type } from 'ol/geom/Geometry'
import { getLength, getArea } from "ol/sphere";
import { Style, Fill, Stroke, Circle } from "ol/style";
import { Draw } from "ol/interaction";
import { Vector } from "ol/layer"
/**
 * 创建一个新的帮助提示框(tooltip)
 * @param {Element} helpTooltipElement  帮助提示框对象
 * @param {ol.Map} map  地图实例
 */
export function createHelpTooltip({ helpTooltipElement, map }: { helpTooltipElement: HTMLElement | null, map: Map }): [HTMLDivElement, Overlay] {
    if (helpTooltipElement && helpTooltipElement.parentNode) {
        helpTooltipElement.parentNode.removeChild(helpTooltipElement);
    }
    const newHelpTooltipElement = document.createElement("div");
    newHelpTooltipElement.className = "tooltip hidden";
    const helpTooltip = new Overlay({
        element: newHelpTooltipElement,
        offset: [15, 0],
        positioning: "center-left",
    });
    map.addOverlay(helpTooltip);
    return [newHelpTooltipElement, helpTooltip]
}


/**
* 创建一个新的测量工具提示框(tooltip)
* @param {Element} measureTooltipElement  测量工具提示框对象
* @param {ol.Map} map  地图实例
*/
export function createMeasureTooltip({ measureTooltipElement, map }: { measureTooltipElement: HTMLElement | null, map: Map }): [HTMLDivElement, Overlay] {
    if (measureTooltipElement && measureTooltipElement.parentNode) {
        measureTooltipElement.parentNode.removeChild(measureTooltipElement);
    }
    const newMeasureTooltipElement = document.createElement("div");
    newMeasureTooltipElement.className = "tooltip tooltip-measure";
    const measureTooltip = new Overlay({
        element: newMeasureTooltipElement,
        offset: [0, -15],
        positioning: "bottom-center",
    });
    map.addOverlay(measureTooltip);
    return [newMeasureTooltipElement, measureTooltip]
}

/**
 * 测量长度输出
 * @param {boolean} checked 是否使用测地学方法测量
 * @param {ol.geom.LineString} line
 * @param {ol.Map} map  地图实例
 * @return {string}
 */
export function formatLength({ checked, line, map }: { checked: boolean, line: LineString, map: Map }): string {
    let length: number;
    if (checked) {
        //若使用测地学方法测量
        const sourceProj = map.getView().getProjection(); //地图数据源投影坐标系
        length = getLength(line, {
            projection: sourceProj,
            radius: 6378137,
        });
    } else {
        length = Math.round(line.getLength() * 100) / 100; //直接得到线的长度
    }
    let output: string;
    if (length > 100) {
        output = Math.round((length / 1000) * 100) / 100 + " " + "km"; //换算成KM单位
    } else {
        output = Math.round(length * 100) / 100 + " " + "m"; //m为单位
    }
    return output; //返回线的长度
}

/**
 * 测量面积输出
 * @param {boolean} checked 是否使用测地学方法测量
 * @param {ol.geom.Polygon} polygon
 * @param {ol.Map} map  地图实例
 * @return {string}
 */
export function formatArea({ checked, polygon, map }: { checked: boolean, polygon: Polygon, map: Map }): string {
    let area: number;
    if (checked) {
        //若使用测地学方法测量
        const sourceProj = map.getView().getProjection(); //地图数据源投影坐标系
        const geom = /** @type {ol.geom.Polygon} */ (
            polygon.clone().transform(sourceProj, "EPSG:4326")
        ); //将多边形要素坐标系投影为EPSG:4326
        area = Math.abs(
            getArea(geom, { projection: sourceProj, radius: 6378137 })
        ); //获取面积
    } else {
        area = polygon.getArea(); //直接获取多边形的面积
    }
    let output: string;
    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; //返回多边形的面积
}
/**
 * 绘制几何图形
 * @param {ol.source.Vector} source 测量绘制层数据源
 * @param {ol.geom.GeometryType} type 几何图形类型
 * @return 返回绘制好的几何图形
 */
export function drawGeometricFigure({ source, type }: { source: VectorSource, type: Type }): Draw {
    return new Draw({
        source, //测量绘制层数据源
        type,
        style: new Style({
            //绘制几何图形的样式
            fill: new Fill({
                color: "rgba(255, 255, 255, 0.2)",
            }),
            stroke: new Stroke({
                color: "rgba(0, 0, 0, 0.5)",
                lineDash: [10, 10],
                width: 2,
            }),
            image: new Circle({
                radius: 5,
                stroke: new Stroke({
                    color: "rgba(0, 0, 0, 0.7)",
                }),
                fill: new Fill({
                    color: "rgba(255, 255, 255, 0.2)",
                }),
            }),
        }),
    });
}

// 创建测量的绘制矢量层
export function createVector(source: VectorSource): Vector<VectorSource> {
    return new Vector({
        source,
        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",
                }),
            }),
        }),
    })
}

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