惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
Forbes - Security
Forbes - Security
WordPress大学
WordPress大学
P
Proofpoint News Feed
T
Threat Research - Cisco Blogs
L
LINUX DO - 热门话题
L
Lohrmann on Cybersecurity
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
大猫的无限游戏
大猫的无限游戏
博客园 - 三生石上(FineUI控件)
P
Privacy International News Feed
A
About on SuperTechFans
T
Tailwind CSS Blog
I
InfoQ
S
Securelist
云风的 BLOG
云风的 BLOG
罗磊的独立博客
Recent Announcements
Recent Announcements
T
The Exploit Database - CXSecurity.com
B
Blog RSS Feed
V
Visual Studio Blog
Know Your Adversary
Know Your Adversary
The GitHub Blog
The GitHub Blog
Jina AI
Jina AI
腾讯CDC
Cyberwarzone
Cyberwarzone
有赞技术团队
有赞技术团队
AWS News Blog
AWS News Blog
博客园 - 【当耐特】
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
F
Full Disclosure
S
Secure Thoughts
博客园 - 司徒正美
J
Java Code Geeks
Y
Y Combinator Blog
Google Online Security Blog
Google Online Security Blog
GbyAI
GbyAI
N
News and Events Feed by Topic
Help Net Security
Help Net Security
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
Project Zero
Project Zero
T
Tenable Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
T
Tor Project blog
MyScale Blog
MyScale Blog
Scott Helme
Scott Helme
小众软件
小众软件
K
Kaspersky official blog

博客园 - ^Mao^

不同缩放下适配 vxe-table 3D饼图 openlayer实现给线的附近添加点,点支持移动 element-plus el-select html导出pdf openlayers增加移动功能 适配 部分页面统计用户访问时长 使用openlayer绘制街景地图 正则表达式--取对应表达式的值 vue2实现el-table-column多级效果 echarts-雷达图 echarts--地图 散点图效果 echarts散点图区域设置 vant 的vant-uploader组件问题 Threejs学习 Echarts-普通地图和3D地图实现 test
openlayers基本使用(街景+标注+绘制)
^Mao^ · 2025-10-11 · via 博客园 - ^Mao^

卫星影像

<template>
  <div class="app">
    <div class="map-container" ref="map_ref"></div>
  </div>
</template>

<script>
import Map from "ol/Map.js";
import TileLayer from "ol/layer/Tile.js";
import View from "ol/View.js";
import XYZ from "ol/source/XYZ.js";

export default {
  data() {
    return {};
  },
  mounted() {
    this.initMap();
  },
  methods: {
    initMap() {
      // const map = new Map({
      //   target: this.$refs.map_ref,
      //   layers: [
      //     new TileLayer({
      //       source: new XYZ({
      //         url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
      //       }),
      //     }),
      //   ],
      //   view: new View({
      //     center: [0, 0],
      //     zoom: 2,
      //   }),
      // });

      // 创建卫星图层
      const satelliteLayer = new TileLayer({
        source: new XYZ({
          url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
          maxZoom: 18,
        }),
        opacity: 0.9,
        zIndex: 1,
      });

      // 创建街道图层作为备用
      const streetLayer = new TileLayer({
        source: new XYZ({
          url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=7&x={x}&y={y}&z={z}",
          maxZoom: 18,
        }),
        zIndex: 0,
        visible: false,
      });
      const map = new Map({
        target: this.$refs.map_ref,
        layers: [satelliteLayer, streetLayer],
        view: new View({
          center: [0, 0],
          zoom: 2,
        }),
      });
    },
  },
};
</script>

<style lang="less" scoped>
.app {
  padding: 10px;
}
.map-container {
  height: calc(100vh - 50px);
  // background-color: orange;
}
</style>

增加标注

基础示例

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>OpenLayers 街景与地名标注</title>
    <!-- 引入 OpenLayers CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.css" />
    <!-- 引入 OpenLayers JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.js"></script>
    <style>
      #map {
        width: 100%;
        height: 800px;
      }
    </style>
  </head>
  <body>
    <div id="map"></div>
    <script>
      // 1.创建地图=============
      var map = new ol.Map({
        target: "map", // 地图渲染的目标 DOM 元素 ID
        layers: [
          // 底图图层,这里使用 OpenStreetMap
          new ol.layer.Tile({
            // source: new ol.source.OSM(),
            source: new ol.source.XYZ({
              url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
            }),
          }),
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([108, 28]), // 地图初始中心点,这里以西南地区大致经纬度为例
          zoom: 8, // 初始缩放级别
        }),
      });

      // 2.1 加载地名 GeoJSON 数据=======================
      var vectorSource = new ol.source.Vector({
        url: "./places.json", // 地名数据文件路径
        format: new ol.format.GeoJSON(),
      });

      // 2.2 创建矢量图层,用于显示地名标注
      var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
          // 为每个地名要素设置样式,包括点样式和文字标签
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "12px Arial",
            fill: new ol.style.Fill({
              color: "#000",
            }),
            stroke: new ol.style.Stroke({
              color: "#fff",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
      });

      // 2.3 将地名标注图层添加到地图
      map.addLayer(vectorLayer);

      // 3. 给地图添加点击事件,模拟街景加载
      map.on("click", function (evt) {
        var coordinate = evt.coordinate;
        // 这里只是模拟,实际需根据点击位置获取街景资源并展示
        alert("点击位置经纬度:" + ol.proj.toLonLat(coordinate));
        // 实际项目中,可在此处调用街景库的 API 加载对应位置的街景
      });

      // 获取地图的缩放级别=================start
      // 获取地图视图对象
      const view = map.getView();
      // 监听change:resolution事件
      view.on("change:resolution", function () {
        const zoom = view.getZoom(); // 获取当前缩放级别
        console.log("zoom=========>", zoom);
      });
      // 获取地图的缩放级别=================end
    </script>
  </body>
</html>

缩放到一定程度后再显示标注

效果图:

image

示例代码:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>OpenLayers 街景与地名标注</title>
    <!-- 引入 OpenLayers CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.css" />
    <!-- 引入 OpenLayers JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.js"></script>
    <style>
      #map {
        width: 100%;
        height: 800px;
      }
    </style>
  </head>
  <body>
    <div class="info">
      当前缩放级别: <span id="zoom-level">0</span><br />
      标注将在缩放级别 ≥ 8 时显示
    </div>
    <div id="map"></div>
    <script>
      // 1.创建地图=============
      var map = new ol.Map({
        target: "map", // 地图渲染的目标 DOM 元素 ID
        layers: [
          // 底图图层,这里使用 OpenStreetMap
          new ol.layer.Tile({
            // source: new ol.source.OSM(),
            source: new ol.source.XYZ({
              url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
            }),
          }),
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([108, 28]), // 地图初始中心点,这里以西南地区大致经纬度为例
          zoom: 8, // 初始缩放级别
        }),
      });

      // 2.1 加载地名 GeoJSON 数据=======================
      var vectorSource = new ol.source.Vector({
        url: "./places.json", // 地名数据文件路径
        format: new ol.format.GeoJSON(),
      });

      // 2.2 创建矢量图层,用于显示地名标注
      var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
          // 为每个地名要素设置样式,包括点样式和文字标签
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "12px Arial",
            fill: new ol.style.Fill({
              color: "#000",
            }),
            stroke: new ol.style.Stroke({
              color: "#fff",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
      });

      // 2.3 将地名标注图层添加到地图
      map.addLayer(vectorLayer);

      // 3. 需求:获取地图的经纬度      (给地图添加点击事件)
      map.on("click", function (evt) {
        var coordinate = evt.coordinate;
        // 这里只是模拟,实际需根据点击位置获取街景资源并展示
        alert("点击位置经纬度:" + ol.proj.toLonLat(coordinate));
        // 实际项目中,可在此处调用街景库的 API 加载对应位置的街景
      });

      // 4. 需求:缩放到一定的程度后再显示标注图层============start
      // 缩放级别显示元素
      var zoomLevelElement = document.getElementById("zoom-level");
      // 控制标注显示的最小缩放级别
      var MIN_ZOOM_FOR_LABELS = 8;
      // 更新标注可见性的函数
      function updateLabelsVisibility() {
        var currentZoom = map.getView().getZoom();
        zoomLevelElement.textContent = currentZoom.toFixed(1);

        // 当缩放级别大于等于设定值时显示标注,否则隐藏
        vectorLayer.setVisible(currentZoom >= MIN_ZOOM_FOR_LABELS);
      }
      // 初始调用一次,设置初始状态
      updateLabelsVisibility();

      // 监听地图的移动结束事件(包括缩放和拖动)
      map.on("moveend", updateLabelsVisibility);
      // 4. 需求:缩放到一定的程度后再显示标注图层============end
    </script>
  </body>
</html>

多边形绘制和标记

基础示例

效果图:

image

示例代码:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>OpenLayers 街景与地名标注</title>
    <!-- 引入 OpenLayers CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.css" />
    <!-- 引入 OpenLayers JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.js"></script>
    <style>
      #map {
        width: 100%;
        height: 800px;
      }

      .info {
        /* position: absolute;
        top: 10px;
        left: 10px; */
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
        /* max-width: 250px; */
      }

      /* 绘制工具 */
      .toolbar {
        /* position: absolute;
        top: 10px;
        right: 10px; */
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
      }
      .toolbar button {
        /* display: block;
        width: 100%; */
        padding: 8px 12px;
        margin-bottom: 5px;
        background: #f5f5f5;
        border: 1px solid #ddd;
        border-radius: 3px;
        cursor: pointer;
        transition: all 0.2s;
      }
      .toolbar button:hover {
        background: #e0e0e0;
      }
      .toolbar button.active {
        background: #4285f4;
        color: white;
        border-color: #4285f4;
      }

      .status-panel {
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
        max-height: 200px;
        overflow-y: auto;
      }
      .feature-count {
        color: #666;
        font-size: 14px;
        margin-bottom: 10px;
      }
    </style>
  </head>
  <body>
    <div class="info">
      当前缩放级别: <span id="zoom-level">0</span><br />
      标注将在缩放级别 ≥ 8 时显示
    </div>
    <div class="toolbar">
      <button id="draw-point">绘制点标记</button>
      <button id="draw-line-string">绘制线</button>
      <button id="draw-polygon">绘制多边形</button>
      <button id="clear">清除所有绘制</button>
    </div>

    <div class="data-panel">
      <h4>绘制数据信息:</h4>
      <pre id="data-display"></pre>
    </div>

    <!-- <div class="data-panel">
      <h4>绘制数据信息:</h4>
      <pre id="latest-data"></pre>
      <div class="history-list">
        <h5>历史绘制记录:</h5>
        <ul id="history-data"></ul>
      </div>
    </div> -->
    <div class="status-panel">
      <div class="feature-count">当前已绘制: <span id="count">0</span> 个要素</div>
      <div>
        <h4>最近绘制数据:</h4>
        <pre id="last-feature-data"></pre>
      </div>
    </div>

    <div id="map"></div>
    <script>
      var map = new ol.Map({
        target: "map",
        layers: [
          // 底图图层,这里使用 OpenStreetMap
          new ol.layer.Tile({
            // source: new ol.source.OSM(),
            source: new ol.source.XYZ({
              url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
            }),
          }),
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([108, 28]),
          zoom: 8,
        }),
      });

      var vectorSource = new ol.source.Vector({
        url: "./places.json", // 地名数据文件路径
        format: new ol.format.GeoJSON(),
      });

      var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "12px Arial",
            fill: new ol.style.Fill({
              color: "#000",
            }),
            stroke: new ol.style.Stroke({
              color: "#fff",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
      });

      map.addLayer(vectorLayer);

      map.on("click", function (evt) {
        var coordinate = evt.coordinate;
        console.log("点击位置经纬度:" + ol.proj.toLonLat(coordinate));
      });

      var zoomLevelElement = document.getElementById("zoom-level");
      var MIN_ZOOM_FOR_LABELS = 8;
      function updateLabelsVisibility() {
        var currentZoom = map.getView().getZoom();
        zoomLevelElement.textContent = currentZoom.toFixed(1);
        vectorLayer.setVisible(currentZoom >= MIN_ZOOM_FOR_LABELS);
      }
      updateLabelsVisibility();
      map.on("moveend", updateLabelsVisibility);

      // 绘制
      // 绘制交互变量
      let draw;
      // 创建矢量数据源和图层
      const vector_source = new ol.source.Vector();
      // 创建矢量图层并关联数据源
      const vector_layer = new ol.layer.Vector({
        source: vector_source,
        style: new ol.style.Style({
          fill: new ol.style.Fill({ color: "rgba(0, 153, 255, 0.2)" }),
          stroke: new ol.style.Stroke({ color: "#0099ff", width: 2 }),
          image: new ol.style.Circle({
            radius: 6,
            fill: new ol.style.Fill({ color: "#0099ff" }),
            stroke: new ol.style.Stroke({ color: "white", width: 1 }),
          }),
        }),
      });
      map.addLayer(vector_layer);

      const dataDisplay = document.getElementById("data-display");

      const countElement = document.getElementById("count");
      const lastFeatureElement = document.getElementById("last-feature-data");

      // 更新要素计数显示
      function updateFeatureCount() {
        const count = vector_source.getFeatures().length;
        countElement.textContent = count;
      }

      // 激活绘制工具的函数
      function activateDraw(type, id) {
        // 移除现有的绘制交互
        if (draw) {
          map.removeInteraction(draw);
        }

        // 清除按钮的active状态
        document.querySelectorAll(".toolbar button").forEach((btn) => {
          btn.classList.remove("active");
        });

        // 如果不是清除操作,则激活相应的绘制工具
        if (type !== "clear") {
          document.getElementById(id).classList.add("active");

          // 创建新的绘制交互
          draw = new ol.interaction.Draw({
            // 绘制的内容会添加到这个数据源
            source: vector_source,
            type: type,
          });
          map.addInteraction(draw);

          // 监听绘制结束事件
          draw.on("drawend", function (event) {
            // 获取绘制的要素
            const feature = event.feature;
            // 获取几何图形
            const geometry = feature.getGeometry();

            // 根据几何类型处理数据
            let data, type;
            switch (geometry.getType()) {
              case "Point":
                type = "点";
                // OpenLayers 默认使用 Web Mercator 投影坐标(米为单位),转换为经纬度坐标(度为单位)
                const coords = ol.proj.toLonLat(geometry.getCoordinates());
                data = `坐标: [${coords[0].toFixed(6)}, ${coords[1].toFixed(6)}]`;
                break;

              case "LineString":
                type = "线";
                // 线的坐标数组
                const lineCoords = geometry
                  .getCoordinates()
                  .map((coord) => ol.proj.toLonLat(coord))
                  .map((coord) => `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`);
                data = `路径点: [\n  ${lineCoords.join(",\n  ")}\n]`;
                break;

              case "Polygon":
                type = "多边形";
                // 多边形的坐标数组(外层数组是多边形环,取第一个环)
                const polygonCoords = geometry
                  .getCoordinates()[0]
                  .map((coord) => ol.proj.toLonLat(coord))
                  .map((coord) => `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`);
                data = `顶点: [\n  ${polygonCoords.join(",\n  ")}\n]`;
                break;
            }

            // 显示数据
            dataDisplay.textContent = `绘制了${type}:\n${data}\n\n历史数据可通过vectorSource获取`;
            lastFeatureElement.textContent = data;
            // 更新要素计数
            // 确保添加完成后再更新计数
            setTimeout(() => {
              updateFeatureCount();
            }, 200);
          });
        }
      }

      // 绑定按钮事件
      document.getElementById("draw-point").addEventListener("click", () => {
        activateDraw("Point", "draw-point");
      });

      document.getElementById("draw-line-string").addEventListener("click", () => {
        activateDraw("LineString", "draw-line-string");
      });

      document.getElementById("draw-polygon").addEventListener("click", () => {
        activateDraw("Polygon", "draw-polygon");
      });

      document.getElementById("clear").addEventListener("click", () => {
        // 清除所有绘制的要素
        vector_source.clear();
        // 移除绘制交互
        if (draw) {
          map.removeInteraction(draw);
          draw = null;
        }
        // 清除按钮的active状态
        document.querySelectorAll(".toolbar button").forEach((btn) => {
          btn.classList.remove("active");
          dataDisplay.textContent = "所有绘制已清除";

          // 更新显示
          lastFeatureElement.textContent = "所有要素已清除";
          updateFeatureCount();
        });
      });
    </script>
  </body>
</html>

增加信息窗

效果图:

image

示例代码:

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <title>OpenLayers 街景与地名标注</title>
    <!-- 引入 OpenLayers CSS -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.css" />
    <!-- 引入 OpenLayers JavaScript -->
    <script src="https://cdn.jsdelivr.net/npm/ol@7.5.1/dist/ol.js"></script>
    <style>
      #map {
        width: 100%;
        height: 800px;
      }

      .info {
        /* position: absolute;
        top: 10px;
        left: 10px; */
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
        /* max-width: 250px; */
      }

      /* 绘制工具 */
      .toolbar {
        /* position: absolute;
        top: 10px;
        right: 10px; */
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
      }
      .toolbar button {
        /* display: block;
        width: 100%; */
        padding: 8px 12px;
        margin-bottom: 5px;
        background: #f5f5f5;
        border: 1px solid #ddd;
        border-radius: 3px;
        cursor: pointer;
        transition: all 0.2s;
      }
      .toolbar button:hover {
        background: #e0e0e0;
      }
      .toolbar button.active {
        background: #4285f4;
        color: white;
        border-color: #4285f4;
      }

      .status-panel {
        background: white;
        padding: 10px;
        border-radius: 5px;
        box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
        max-height: 200px;
        overflow-y: auto;
      }
      .feature-count {
        color: #666;
        font-size: 14px;
        margin-bottom: 10px;
      }

      /* 信息弹窗样式 */
      .popup {
        position: absolute;
        background-color: white;
        padding: 10px;
        border: 1px solid #ccc;
        border-radius: 5px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        pointer-events: auto;
        display: none;
      }
      .popup-content {
        min-width: 200px;
      }
      .popup-closer {
        position: absolute;
        top: 5px;
        right: 5px;
        cursor: pointer;
        font-size: 16px;
      }
    </style>
  </head>
  <body>
    <div class="info">
      当前缩放级别: <span id="zoom-level">0</span><br />
      标注将在缩放级别 ≥ 8 时显示
    </div>
    <div class="toolbar">
      <button id="draw-point">绘制点标记</button>
      <button id="draw-line-string">绘制线</button>
      <button id="draw-polygon">绘制多边形</button>
      <button id="clear">清除所有绘制</button>
      <button id="save">保存</button>
    </div>

    <div class="data-panel">
      <h4>绘制数据信息:</h4>
      <pre id="data-display"></pre>
    </div>

    <!-- <div class="data-panel">
      <h4>绘制数据信息:</h4>
      <pre id="latest-data"></pre>
      <div class="history-list">
        <h5>历史绘制记录:</h5>
        <ul id="history-data"></ul>
      </div>
    </div> -->
    <div class="status-panel">
      <div class="feature-count">当前已绘制: <span id="count">0</span> 个要素</div>
      <div>
        <h4>最近绘制数据:</h4>
        <pre id="last-feature-data"></pre>
      </div>
    </div>

    <!-- 信息弹窗 -->
    <div id="popup" class="popup">
      <a href="#" class="popup-closer" onclick="hidePopup()">&times;</a>
      <div id="popup-content" class="popup-content"></div>
    </div>

    <div id="map"></div>
    <script>
      var map = new ol.Map({
        target: "map",
        layers: [
          // 底图图层,这里使用 OpenStreetMap
          new ol.layer.Tile({
            // source: new ol.source.OSM(),
            source: new ol.source.XYZ({
              url: "https://webst0{1-4}.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}",
            }),
          }),
        ],
        view: new ol.View({
          center: ol.proj.fromLonLat([108, 28]),
          zoom: 8,
        }),
      });

      var vectorSource = new ol.source.Vector({
        url: "./places.json", // 地名数据文件路径
        format: new ol.format.GeoJSON(),
      });

      var vectorLayer = new ol.layer.Vector({
        source: vectorSource,
        style: function (feature) {
          var textStyle = new ol.style.Text({
            text: feature.get("name"), // 获取地名属性
            font: "12px Arial",
            fill: new ol.style.Fill({
              color: "#000",
            }),
            stroke: new ol.style.Stroke({
              color: "#fff",
              width: 2,
            }),
            offsetY: -15, // 文字相对于点的垂直偏移
          });

          var pointStyle = new ol.style.Style({
            image: new ol.style.Circle({
              radius: 5,
              fill: new ol.style.Fill({
                color: "#ff0000",
              }),
            }),
            text: textStyle,
          });

          return pointStyle;
        },
      });

      map.addLayer(vectorLayer);

      map.on("click", function (evt) {
        var coordinate = evt.coordinate;
        console.log("点击位置经纬度:" + ol.proj.toLonLat(coordinate));

        // 检查点击位置是否有要素
        map.forEachFeatureAtPixel(evt.pixel, function (feature) {
          // 核心过滤逻辑:排除临时要素(只有带 isFinished: true 的才是正式要素)
          // 如果点击的是多边形
          // if (feature.get("isFinished") && feature.getGeometry() instanceof ol.geom.Polygon) {
          //   showPopup(evt.coordinate, feature);
          //   return true; // 停止遍历
          // }
          if (feature.get("isFinished")) {
            showPopup(evt.coordinate, feature);
            return true; // 停止遍历
          }
        });
      });

      var zoomLevelElement = document.getElementById("zoom-level");
      var MIN_ZOOM_FOR_LABELS = 8;
      function updateLabelsVisibility() {
        var currentZoom = map.getView().getZoom();
        zoomLevelElement.textContent = currentZoom.toFixed(1);
        vectorLayer.setVisible(currentZoom >= MIN_ZOOM_FOR_LABELS);
      }
      updateLabelsVisibility();
      map.on("moveend", updateLabelsVisibility);

      // 绘制
      // 绘制交互变量
      let draw;
      // 创建矢量数据源和图层
      const vector_source = new ol.source.Vector();
      // 创建矢量图层并关联数据源
      const vector_layer = new ol.layer.Vector({
        source: vector_source,
        style: new ol.style.Style({
          fill: new ol.style.Fill({ color: "rgba(0, 153, 255, 0.2)" }),
          stroke: new ol.style.Stroke({ color: "#0099ff", width: 2 }),
          image: new ol.style.Circle({
            radius: 6,
            fill: new ol.style.Fill({ color: "#0099ff" }),
            stroke: new ol.style.Stroke({ color: "white", width: 1 }),
          }),
        }),
      });
      map.addLayer(vector_layer);

      const dataDisplay = document.getElementById("data-display");

      const countElement = document.getElementById("count");
      const lastFeatureElement = document.getElementById("last-feature-data");

      const saveElement = document.getElementById("save");

      // 更新要素计数显示
      function updateFeatureCount() {
        const count = vector_source.getFeatures().length;
        countElement.textContent = count;
      }

      // 计算多边形面积(平方米)
      function calculateArea(geometry) {
        const area = geometry.getArea();
        // 转换为平方米(如果需要)
        return Math.round(area) + " 平方米";
      }

      // 激活绘制工具的函数
      function activateDraw(type, id) {
        // 移除现有的绘制交互
        if (draw) {
          map.removeInteraction(draw);
        }

        // 清除按钮的active状态
        document.querySelectorAll(".toolbar button").forEach((btn) => {
          btn.classList.remove("active");
        });

        // 如果不是清除操作,则激活相应的绘制工具
        if (type !== "clear") {
          document.getElementById(id).classList.add("active");

          // 创建新的绘制交互
          draw = new ol.interaction.Draw({
            // 绘制的内容会添加到这个数据源
            source: vector_source,
            type: type,
          });
          map.addInteraction(draw);

          // 监听绘制结束事件
          draw.on("drawend", function (event) {
            // event.stopPropagation();
            draw.setActive(false);

            // 获取绘制的要素
            const feature = event.feature;
            // 获取几何图形
            const geometry = feature.getGeometry();

            // 给正式要素添加标识(用于后续弹窗过滤)
            feature.set("isFinished", true);

            // 添加其他属性(如面积)
            // 多边形
            // feature.set("area", Math.round(geometry.getArea()) + " 平方米");
            // feature.set("name", "多边形" + vector_source.getFeatures().length);
            feature.set("test_data", {
              name: "zs",
              age: 36,
              friend: ["kobe", "lili"],
            });

            // 根据几何类型处理数据
            let data, type;
            switch (geometry.getType()) {
              case "Point":
                type = "点";
                // OpenLayers 默认使用 Web Mercator 投影坐标(米为单位),转换为经纬度坐标(度为单位)
                const coords = ol.proj.toLonLat(geometry.getCoordinates());
                data = `坐标: [${coords[0].toFixed(6)}, ${coords[1].toFixed(6)}]`;
                break;

              case "LineString":
                type = "线";
                // 线的坐标数组
                const lineCoords = geometry
                  .getCoordinates()
                  .map((coord) => ol.proj.toLonLat(coord))
                  .map((coord) => `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`);
                data = `路径点: [\n  ${lineCoords.join(",\n  ")}\n]`;
                break;

              case "Polygon":
                type = "多边形";
                // 多边形的坐标数组(外层数组是多边形环,取第一个环)
                const polygonCoords = geometry
                  .getCoordinates()[0]
                  .map((coord) => ol.proj.toLonLat(coord))
                  .map((coord) => `[${coord[0].toFixed(6)}, ${coord[1].toFixed(6)}]`);
                data = `顶点: [\n  ${polygonCoords.join(",\n  ")}\n]`;
                break;
            }

            // 显示数据
            dataDisplay.textContent = `绘制了${type}:\n${data}\n\n历史数据可通过vectorSource获取`;
            lastFeatureElement.textContent = data;
            // 更新要素计数
            // 确保添加完成后再更新计数
            setTimeout(() => {
              updateFeatureCount();
            }, 200);
          });
        }
      }

      // 绑定按钮事件
      document.getElementById("draw-point").addEventListener("click", () => {
        activateDraw("Point", "draw-point");
      });

      document.getElementById("draw-line-string").addEventListener("click", () => {
        activateDraw("LineString", "draw-line-string");
      });

      document.getElementById("draw-polygon").addEventListener("click", () => {
        activateDraw("Polygon", "draw-polygon");
      });

      document.getElementById("clear").addEventListener("click", () => {
        // 清除所有绘制的要素
        vector_source.clear();
        // 移除绘制交互
        if (draw) {
          map.removeInteraction(draw);
          draw = null;
        }
        // 清除按钮的active状态
        document.querySelectorAll(".toolbar button").forEach((btn) => {
          btn.classList.remove("active");
          dataDisplay.textContent = "所有绘制已清除";

          // 更新显示
          lastFeatureElement.textContent = "所有要素已清除";
          updateFeatureCount();
        });
      });

      document.getElementById("save").addEventListener("click", () => {
        if (draw) {
          map.removeInteraction(draw);
        }
      });

      // 显示弹窗
      function showPopup(coordinate, feature) {
        const popup = document.getElementById("popup");
        const popupContent = document.getElementById("popup-content");

        // 设置弹窗内容
        // 示例:多边形
        // popupContent.innerHTML = `
        //         <h3>${feature.get("name")}</h3>
        //         <p>面积: ${feature.get("area")}</p>
        //         <p>ID: ${feature.getId()}</p>
        //     `;
        const test_data = feature.get("test_data");
        popupContent.innerHTML = `
                <h1>测试数据</h1>
                <h3>姓名:${test_data.name}</h3>
                <h3>年龄:${test_data.age}</h3>
                <h3>朋友:${JSON.stringify(test_data.friend)}</h3>
            `;
        console.log("dddddd==>", feature.get("test_data"));

        // 设置弹窗位置
        const overlay = new ol.Overlay({
          element: popup,
          positioning: "bottom-center",
          stopEvent: false,
          offset: [0, -10],
        });
        map.addOverlay(overlay);
        overlay.setPosition(coordinate);

        // 显示弹窗
        popup.style.display = "block";
      }

      // 隐藏弹窗
      function hidePopup() {
        const popup = document.getElementById("popup");
        popup.style.display = "none";
      }

      // 鼠标悬停时改变光标样式
      map.on("pointermove", function (e) {
        const pixel = map.getEventPixel(e.originalEvent);
        const hit = map.hasFeatureAtPixel(pixel);
        map.getTargetElement().style.cursor = hit ? "pointer" : "";
      });
    </script>
  </body>
</html>

更多

可参考:https://mp.weixin.qq.com/s/bwdGYOF4Oy2B-cNyX8JnDQ