Skip to content

测量工具

上次更新 2025年8月20日星期三 3:16:20 字数 0 字 时长 0 分钟

本章将介绍如何在 OpenLayers 中实现测量工具功能,包括距离测量和面积测量。

示例

新标签页预览

代码实现

vue
<template>
  <div class="h-screen">
    <div id="map" class="w-full h-full"></div>
  </div>
</template>

<script setup>
import { onMounted } from "vue";
import "ol/ol.css";
import Map from "ol/Map";
import View from "ol/View";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
import VectorSource from "ol/source/Vector";
import VectorLayer from "ol/layer/Vector";
import { Draw } from "ol/interaction";
import { fromLonLat } from "ol/proj";
import { Circle as CircleStyle, Fill, Stroke, Style } from "ol/style";
import { Control } from "ol/control";
import { getLength, getArea } from "ol/sphere";
import Overlay from "ol/Overlay";

onMounted(() => {
  // 创建地图
  const map = new Map({
    target: "map",
    view: new View({
      center: fromLonLat([117.33083, 39.094899]), // 天津
      zoom: 12,
    }),
  });

  // 添加天地图图层
  const tdtLayer = new TileLayer({
    source: new XYZ({
      url: "http://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk=d32c6748c80f81a44acd8633cea41dfd",
    }),
  });
  map.addLayer(tdtLayer);

  // 创建绘图图层
  const source = new VectorSource();
  const vector = new VectorLayer({
    source: source,
    style: new Style({
      fill: new Fill({
        color: "rgba(24, 144, 255, 0.2)",
      }),
      stroke: new Stroke({
        color: "#1890ff",
        width: 2,
      }),
      image: new CircleStyle({
        radius: 7,
        fill: new Fill({
          color: "#1890ff",
        }),
      }),
    }),
  });
  map.addLayer(vector);

  // 创建测量控件
  const measureControls = document.createElement("div");
  measureControls.className = "ol-control measure-controls";

  const lengthButton = document.createElement("button");
  lengthButton.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M2 12h20M6 8v8M12 6v12M18 8v8"/></svg>`;
  lengthButton.title = "测距离";
  lengthButton.addEventListener("click", function () {
    areaButton.classList.remove("active");
    lengthButton.classList.add("active");
    addInteraction("LineString");
  });

  const areaButton = document.createElement("button");
  areaButton.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M3 3h18v18H3z"/><path d="M3 9h18M3 15h18M9 3v18M15 3v18"/></svg>`;
  areaButton.title = "测面积";
  areaButton.addEventListener("click", function () {
    lengthButton.classList.remove("active");
    areaButton.classList.add("active");
    addInteraction("Polygon");
  });

  measureControls.appendChild(lengthButton);
  measureControls.appendChild(areaButton);

  map.addControl(
    new Control({
      element: measureControls,
    })
  );

  let draw;
  let sketch;
  let helpTooltipElement;
  let helpTooltip;
  let measureTooltipElement;
  let measureTooltip;

  const formatLength = function (line) {
    const length = getLength(line);
    let output;
    if (length > 100) {
      output = Math.round((length / 1000) * 100) / 100 + " km";
    } else {
      output = Math.round(length * 100) / 100 + " m";
    }
    return output;
  };

  const formatArea = function (polygon) {
    const area = getArea(polygon);
    let output;
    if (area > 10000) {
      output = Math.round((area / 1000000) * 100) / 100 + " km²";
    } else {
      output = Math.round(area * 100) / 100 + " m²";
    }
    return output;
  };

  function addInteraction(type) {
    if (draw) {
      map.removeInteraction(draw);
    }
    draw = new Draw({
      source: source,
      type: type,
      style: new Style({
        fill: new Fill({
          color: "rgba(24, 144, 255, 0.2)",
        }),
        stroke: new Stroke({
          color: "#1890ff",
          lineDash: [6, 6],
          width: 2,
        }),
        image: new CircleStyle({
          radius: 5,
          stroke: new Stroke({
            color: "#1890ff",
          }),
          fill: new Fill({
            color: "#fff",
          }),
        }),
      }),
    });
    map.addInteraction(draw);

    createMeasureTooltip();
    createHelpTooltip();

    draw.on("drawstart", function (evt) {
      sketch = evt.feature;

      let tooltipCoord = evt.coordinate;

      sketch.getGeometry().on("change", function (evt) {
        const geom = evt.target;
        let output;
        if (geom instanceof ol.geom.Polygon) {
          output = formatArea(geom);
          tooltipCoord = geom.getInteriorPoint().getCoordinates();
        } else if (geom instanceof ol.geom.LineString) {
          output = formatLength(geom);
          tooltipCoord = geom.getLastCoordinate();
        }
        measureTooltipElement.innerHTML = output;
        measureTooltip.setPosition(tooltipCoord);
      });
    });

    draw.on("drawend", function () {
      measureTooltipElement.className = "ol-tooltip ol-tooltip-static";
      measureTooltip.setOffset([0, -7]);
      sketch = null;
      measureTooltipElement = null;
      createMeasureTooltip();
    });
  }

  function createHelpTooltip() {
    if (helpTooltipElement) {
      helpTooltipElement.parentNode.removeChild(helpTooltipElement);
    }
    helpTooltipElement = document.createElement("div");
    helpTooltipElement.className = "ol-tooltip";
    helpTooltip = new Overlay({
      element: helpTooltipElement,
      offset: [15, 0],
      positioning: "center-left",
    });
    map.addOverlay(helpTooltip);
  }

  function createMeasureTooltip() {
    if (measureTooltipElement) {
      measureTooltipElement.parentNode.removeChild(measureTooltipElement);
    }
    measureTooltipElement = document.createElement("div");
    measureTooltipElement.className = "ol-tooltip ol-tooltip-measure";
    measureTooltip = new Overlay({
      element: measureTooltipElement,
      offset: [0, -15],
      positioning: "bottom-center",
    });
    map.addOverlay(measureTooltip);
  }
});
</script>

<style scoped>
.h-screen {
  height: 24rem;
}

.w-full {
  width: 100%;
}

.h-full {
  height: 100%;
}

:deep(.ol-tooltip) {
  position: relative;
  background: rgba(0, 0, 0, 0.75);
  border-radius: 6px;
  color: #fff;
  padding: 6px 12px;
  opacity: 0.9;
  white-space: nowrap;
  font-size: 13px;
  font-weight: 500;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
  backdrop-filter: blur(4px);
}

:deep(.measure-controls) {
  top: 65px;
  left: 0.5em;
  background: white;
  padding: 8px;
  border-radius: 8px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}

:deep(.measure-controls button) {
  background-color: #fff;
  border: 1px solid #e8e8e8;
  color: #595959;
  padding: 8px;
  cursor: pointer;
  border-radius: 4px;
  margin: 3px;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.3s ease;
  min-width: 36px;
  min-height: 36px;
}

:deep(.measure-controls button:hover) {
  background-color: #f5f5f5;
  border-color: #1890ff;
  color: #1890ff;
}

:deep(.measure-controls button svg) {
  width: 22px;
  height: 22px;
  transition: all 0.3s ease;
}

:deep(.measure-controls button.active) {
  background-color: #1890ff;
  border-color: #1890ff;
  color: white;
}

:deep(.measure-controls button.active svg) {
  fill: white;
  stroke: white;
}

:deep(.ol-tooltip-measure) {
  background: #1890ff;
  color: white;
  border: none;
}

:deep(.ol-tooltip-static) {
  background-color: rgba(0, 0, 0, 0.85);
  color: white;
  border: 1px solid white;
}
</style>
关注公众号