vite + vue3 OpenLayers 常用方法 - 特殊使用教程

目录

  • 使用 瓦片图层创建地图
  • 添加预定义坐标系(如 EPSG:4490)
  • ol 事件(单击事件)
  • ol 地理坐标和像素坐标转化, 并控制视图
  • ol 在页面绘制区域
  • 加载 geojson 数据
  • geojson 图层点击某一块展示该块的信息
  • 动态绘制点, 线, 面, 图片, 文字
  • 动态绘制的要素 (Feature) 的事件 (如hover时显示弹窗)

使用 瓦片图层创建地图

<template>
  <div id="mapContainer"></div>
</template>

<script setup>
import { onMounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";

let map = null;

// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4326", // 坐标系
    }),
  });
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

添加预定义坐标系(如 EPSG:4490)

对于 OpenLayers 未内置的坐标系,需使用 proj4 库扩展:

npm install proj4

使用说明:

import proj4 from 'proj4';
import { register } from 'ol/proj/proj4';

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs('EPSG:4490', '+proj=longlat +ellps=GRS80 +no_defs');
register(proj4); // 注册到OpenLayers

// 在地图中使用
const map = new Map({
  view: new View({
    projection: 'EPSG:4490',
    center: [116.39, 39.9], // 直接使用EPSG:4490坐标
    zoom: 10
  })
});

例子:

<template>
  <div id="mapContainer"></div>
</template>

<script setup>
import { onMounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;

// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
  });
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

ol 事件(单击事件)

OpenLayers 中的事件对象继承自 ol.Object.Event,不同类型的事件会携带特定的属性

使用说明:

/**
 * 地图点击事件
 */
const addSingleClick = () => {
  map.on("singleclick", (e) => {
    console.log(e, "e");
    const coord = e.coordinate; // 经纬度坐标
    console.log(coord, "coord");

    const pixel = e.pixel; // 像素坐标
    console.log(pixel, "pixel");

    const originalEvent = e.originalEvent; // 原始事件
    console.log(originalEvent, "originalEvent");

    const feature = map.forEachFeatureAtPixel(pixel, (feature) => {
      return feature;
    }); // 点击获取要素

    if (feature) {  // 判断是否有要素
      console.log(feature, "feature");
      const properties = feature.getProperties(); // 获取要素属性
      console.log(properties, "properties");
    }
  });
}

示例:

<template>
  <div id="mapContainer"></div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
// 引入样式
import { Style, Fill, Stroke } from "ol/style";


// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;

/**
 * 地图点击事件
 */
const addSingleClick = () => {
  map.on("singleclick", (e) => {
    console.log(e, "e");
    const coord = e.coordinate; // 经纬度坐标
    console.log(coord, "coord");

    const pixel = e.pixel; // 像素坐标
    console.log(pixel, "pixel");

    const originalEvent = e.originalEvent; // 原始事件
    console.log(originalEvent, "originalEvent");

    const feature = map.forEachFeatureAtPixel(pixel, (feature) => {
      return feature;
    }); // 点击获取要素

    if (feature) {  // 判断是否有要素
      console.log(feature, "feature");
      const properties = feature.getProperties(); // 获取要素属性
      console.log(properties, "properties");
    }
  });
}

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
  });

  addSingleClick();

});

onUnmounted(() => {
  // 清理地图资源
  if (map) {
    map.setTarget(null);
    map = null;
  }
});


// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

ol 地理坐标和像素坐标转化, 并控制视图

使用说明:

/**
 * 绑定事件, ol 地理坐标和像素坐标转化, 并控制视图
 */
const bind = () => {
  // 监听地图的点击事件
  map.on("click", (evt) => {

    console.log(evt, "evt");

    // 获取点击的坐标
    const coordinate = evt.coordinate;
    console.log(coordinate, "coordinate");

    // 获取点击的像素坐标
    const pixel = evt.pixel;
    console.log(pixel, "pixel");

    // 获取点击的像素坐标
    const pixel2 = map.getPixelFromCoordinate(coordinate);
    console.log(pixel2, "pixel2");

  })

  const chinaExtent4490 = [73, 18, 135, 53]; // [minX, minY, maxX, maxY] 格式,单位:度

  map.getView().fit(chinaExtent4490, {
    padding: [100, 100, 100, 100], // 可选,设置地图边界与地图容器的间距
    duration: 1000, // 可选,设置动画过渡时间
    maxZoom: 10, // 可选,设置最大缩放级别
    minZoom: 6, // 可选,设置最小缩放级别
  })
}

示例:

<template>
  <div id="mapContainer"></div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";


// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;


/**
 * 绑定事件, ol 地理坐标和像素坐标转化, 并控制视图
 */
const bind = () => {
  // 监听地图的点击事件
  map.on("click", (evt) => {

    console.log(evt, "evt");

    // 获取点击的坐标
    const coordinate = evt.coordinate;
    console.log(coordinate, "coordinate");

    // 获取点击的像素坐标
    const pixel = evt.pixel;
    console.log(pixel, "pixel");

    // 获取点击的像素坐标
    const pixel2 = map.getPixelFromCoordinate(coordinate);
    console.log(pixel2, "pixel2");

  })

  const chinaExtent4490 = [73, 18, 135, 53]; // [minX, minY, maxX, maxY] 格式,单位:度

  map.getView().fit(chinaExtent4490, {
    padding: [100, 100, 100, 100], // 可选,设置地图边界与地图容器的间距
    duration: 1000, // 可选,设置动画过渡时间
    maxZoom: 10, // 可选,设置最大缩放级别
    minZoom: 6, // 可选,设置最小缩放级别
  })
}


onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
      minZoom: 5, // 最小缩放级别
      maxZoom: 16, // 最大缩放级别
    }),
  });

  bind();

});

onUnmounted(() => {
  // 清理地图资源
  if (map) {
    map.setTarget(null);
    map = null;
  }
});


// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

ol 在页面绘制区域

在 OpenLayers 中绘制区域(多边形)主要通过ol/interaction/Draw模块实现。下面我将为你提供完整的实现方案,包括基础绘制、样式定制和交互控制。

使用说明:

const drawLayer = () => {
  const style = new Style({
    fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
    stroke: new Stroke({ color: "#ff0000", width: 2, lineDash: [10] }),
  }); // 创建样式

  const source = new VectorSource({ wrapX: false }); // 创建矢量数据源
  const vector = new VectorLayer({
    source: source,
    style: style,
    zIndex: 9999,
  }); // 创建矢量图层

  map.addLayer(vector); // 添加图层

  const draw = new Draw({
    source: source,
    type: "Polygon",
  }); // 创建绘制交互

  map.addInteraction(draw); // 添加交互

  draw.on("drawend", (e) => {
    const feature = e.feature; // 获取绘制的要素
    const geometry = feature.getGeometry(); // 获取要素的几何图形
    console.log(geometry);

    const fw = geometry.getCoordinates(); // 获取要素的坐标数组
    console.log(JSON.stringify(fw, null, 2));

    draw.setActive(false); // 关闭绘制
  });

  return vector;
};

例子:

<template>
  <div id="mapContainer"></div>

  <div class="draw" @click="drawLayer"></div>
</template>

<script setup>
import { onMounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";
// 引入绘制交互
import { Draw } from "ol/interaction";
import { Style, Stroke, Fill } from "ol/style";
// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;

// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});

const drawLayer = () => {
  const style = new Style({
    fill: new Fill({ color: "rgba(255, 0, 0, 0.1)" }),
    stroke: new Stroke({ color: "#ff0000", width: 2, lineDash: [10] }),
  }); // 创建样式

  const source = new VectorSource({ wrapX: false }); // 创建矢量数据源
  const vector = new VectorLayer({
    source: source,
    style: style,
    zIndex: 9999,
  }); // 创建矢量图层

  map.addLayer(vector); // 添加图层

  const draw = new Draw({
    source: source,
    type: "Polygon",
  }); // 创建绘制交互

  map.addInteraction(draw); // 添加交互

  draw.on("drawend", (e) => {
    const feature = e.feature; // 获取绘制的要素
    const geometry = feature.getGeometry(); // 获取要素的几何图形
    console.log(geometry);

    const fw = geometry.getCoordinates(); // 获取要素的坐标数组
    console.log(JSON.stringify(fw, null, 2));

    draw.setActive(false); // 关闭绘制
  });

  return vector;
};

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
  });
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}

.draw {
  width: 50px;
  height: 50px;
  background-color: red;
  position: absolute;
  bottom: 50px;
  left: 50px;
  z-index: 9999;
  opacity: 0.5;
  border-radius: 10px;
}
</style>

加载 geojson 数据

使用说明:

/**
 * 使用外部url geojson 数据绘制图层
 */
const createGeoJSONLayer = () => {
  const vectorSource = new VectorSource({
    wrapX: false,
    url: "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json",
    format: new GeoJSON(),
  });
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      fill: new Fill({
        color: "rgba(255, 255, 255, 0)",
      }),
      stroke: new Stroke({
        color: "#6495ED",
        width: 2,
      }),
    }),
  });
  map.addLayer(vectorLayer);
  return vectorLayer;
};

/**
 * 本地 geojson 文件绘制图层
 */
const createFileGeoJSONLayer = () => {
  const vectorSource = new VectorSource({
    wrapX: false,
    features: new GeoJSON().readFeatures(CityFull),
  });
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      fill: new Fill({
        color: "rgba(255, 255, 255, 0)",
      }),
      stroke: new Stroke({
        color: "#FFDAB9",
        width: 2,
      }),
    }),
  });
  map.addLayer(vectorLayer);
  return vectorLayer;
};

示例:

<template>
  <div id="mapContainer"></div>

  <div class="draw" @click="drawLayer"></div>
</template>

<script setup>
import { onMounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
// 引入样式
import { Style, Fill, Stroke } from "ol/style";
// 引入 GeoJSON 解析器
import GeoJSON from "ol/format/GeoJSON";
// 引入 GeoJSON 本地文件
import CityFull from "@/assets/100000_full_city.json";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;

/**
 * 使用外部url geojson 数据绘制图层
 */
const createGeoJSONLayer = () => {
  const vectorSource = new VectorSource({
    wrapX: false,
    url: "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json",
    format: new GeoJSON(),
  });
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      fill: new Fill({
        color: "rgba(255, 255, 255, 0)",
      }),
      stroke: new Stroke({
        color: "#6495ED",
        width: 2,
      }),
    }),
  });
  map.addLayer(vectorLayer);
  return vectorLayer;
};

/**
 * 本地 geojson 文件绘制图层
 */
const createFileGeoJSONLayer = () => {
  const vectorSource = new VectorSource({
    wrapX: false,
    features: new GeoJSON().readFeatures(CityFull),
  });
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      fill: new Fill({
        color: "rgba(255, 255, 255, 0)",
      }),
      stroke: new Stroke({
        color: "#FFDAB9",
        width: 2,
      }),
    }),
  });
  map.addLayer(vectorLayer);
  return vectorLayer;
};

// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
  });

  createGeoJSONLayer();
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

geojson 图层点击某一块展示该块的信息

要实现点击 GeoJSON 图层显示信息,我们需要:

  1. 监听地图的点击事件

  2. 检查点击位置是否在某个 GeoJSON 要素上

  3. 如果是,则获取该要素的属性信息

  4. 显示信息(可以使用弹窗、悬浮框等方式)

使用说明:

/**
 * geojson 图层点击某一块展示该块的信息
 */
const clickGeoJSONLayer = () => {
  const style = new Style({
    fill: new Fill({
      color: "rgba(178, 34, 34, 0.4)",
    }),
    stroke: new Stroke({
      color: "#3399CC",
      width: 1.25,
    }),
  });

  const vectorSource = new VectorSource({ wrapX: false });

  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: style,
  });

  map.addLayer(vectorLayer);

  map.on("singleclick", function (event) {
    // 获取点击位置的坐标
    const coordinate = event.coordinate;

    // 设置弹窗位置
    popup.setPosition(coordinate);

    // 清空高亮图层
    vectorSource.clear();

    // 检查点击位置是否在图层上的某个要素上
    map.forEachFeatureAtPixel(event.pixel, (feature) => {
      // 获取要素的属性信息
      const properties = feature.getProperties();
      console.log(properties, "properties");

      // 更新弹窗内容
      document.getElementById("popup-content").innerHTML = `<p>${
        properties.name || "未知区域"
      }</p>`;

      // 创建高亮要素
      const newFeature = new Feature({
        geometry: feature.getGeometry(),
      });

      // 将新要素添加到高亮图层
      vectorSource.addFeature(newFeature);

      // 阻止事件冒泡,确保只处理第一个找到的要素
      return true;
    });
  });

  return vectorLayer;
};

示例:

<template>
  <div id="mapContainer"></div>
  <div id="popup" class="ol-popup">
    <a href="#" id="popup-closer" class="ol-popup-closer"></a>
    <div id="popup-content"></div>
  </div>
  <div class="draw" @click="drawLayer"></div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
// 引入所需模块
import { Map, View, Overlay } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
// 引入样式
import { Style, Fill, Stroke } from "ol/style";
// 引入 GeoJSON 解析器
import GeoJSON from "ol/format/GeoJSON";
// 引入要素
import Feature from "ol/Feature";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;
let popup = null;

/**
 * 使用外部url geojson 数据绘制图层
 */
const createGeoJSONLayer = () => {
  const vectorSource = new VectorSource({
    wrapX: false,
    // url: "https://geo.datav.aliyun.com/areas_v3/bound/100000_full.json", // 省份
    url: "https://geo.datav.aliyun.com/areas_v3/bound/100000_full_city.json", // 城市
    format: new GeoJSON(),
  });
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      fill: new Fill({
        color: "rgba(255, 255, 255, 0)",
      }),
      stroke: new Stroke({
        color: "#6495ED",
        width: 2,
      }),
    }),
  });
  map.addLayer(vectorLayer);
  return vectorLayer;
};

/**
 * geojson 图层点击某一块展示该块的信息
 */
const clickGeoJSONLayer = () => {
  const style = new Style({
    fill: new Fill({
      color: "rgba(178, 34, 34, 0.4)",
    }),
    stroke: new Stroke({
      color: "#3399CC",
      width: 1.25,
    }),
  });

  const vectorSource = new VectorSource({ wrapX: false });

  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: style,
  });

  map.addLayer(vectorLayer);

  map.on("singleclick", function (event) {
    // 获取点击位置的坐标
    const coordinate = event.coordinate;

    // 设置弹窗位置
    popup.setPosition(coordinate);

    // 清空高亮图层
    vectorSource.clear();

    // 检查点击位置是否在图层上的某个要素上
    map.forEachFeatureAtPixel(event.pixel, (feature) => {
      // 获取要素的属性信息
      const properties = feature.getProperties();
      console.log(properties, "properties");

      // 更新弹窗内容
      document.getElementById("popup-content").innerHTML = `<p>${
        properties.name || "未知区域"
      }</p>`;

      // 创建高亮要素
      const newFeature = new Feature({
        geometry: feature.getGeometry(),
      });

      // 将新要素添加到高亮图层
      vectorSource.addFeature(newFeature);

      // 阻止事件冒泡,确保只处理第一个找到的要素
      return true;
    });
  });

  return vectorLayer;
};

// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});

onMounted(() => {
  // 创建弹窗元素
  const popupElement = document.getElementById("popup");
  const popupCloser = document.getElementById("popup-closer");

  // 关闭弹窗事件
  popupCloser.onclick = function () {
    popup.setPosition(undefined);
    popupCloser.blur();
    return false;
  };

  // 初始化弹窗
  popup = new Overlay({
    element: popupElement,
    autoPan: true,
    autoPanAnimation: {
      duration: 250,
    },
  });

  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
    overlays: [popup], // 添加弹窗
  });

  createGeoJSONLayer();
  clickGeoJSONLayer();
});

onUnmounted(() => {
  // 清理地图资源
  if (map) {
    map.setTarget(null);
    map = null;
  }
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}

.ol-popup {
  position: absolute;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  padding: 15px;
  border-radius: 10px;
  border: 1px solid #cccccc;
  bottom: 12px;
  left: -50px;
  min-width: 280px;
}

.ol-popup:after,
.ol-popup:before {
  top: 100%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}

.ol-popup:after {
  border-top-color: white;
  border-width: 10px;
  left: 48px;
  margin-left: -10px;
}

.ol-popup:before {
  border-top-color: #cccccc;
  border-width: 11px;
  left: 48px;
  margin-left: -11px;
}

.ol-popup-closer {
  text-decoration: none;
  position: absolute;
  top: 2px;
  right: 8px;
}

.ol-popup-closer:after {
  content: "✖";
}
</style>

动态绘制点, 线, 面, 图片, 文字

使用说明:

/**
 * 地图点击事件
 */
const addSingleClick = () => {

  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      stroke: new Stroke({
        color: 'rgba(0, 0, 255, 1.0)',
        width: 2,
        lineDash: [10, 10]
      }),
      fill: new Fill({
        color: 'rgba(0, 0, 255, 0.2)'
      }),
      image: new Circle({
        radius: 7,
        fill: new Fill({
          color: '#ffcc33',
        }),
        stroke: new Stroke({
          color: '#ea679a',
          width: 2,
        }),
      })
    })
  })

  map.addLayer(vectorLayer)

  map.on("singleclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的要素

    // // 点要素
    // const feature = new Feature({
    //   geometry: new Point(coord),
    //   name: '单击点',
    // })

    // 图片 icon 点, 以及文字
    // const feature = new Feature({
    //   geometry: new Point(coord),
    //   name: '单击点',
    // })

    // // 在 OpenLayers 中,样式需要通过 feature.setStyle() 
    // // 方法或图层的样式配置来设置而不是作为要素的属性。
    // feature.setStyle(new Style({
    //   image: new Icon({
    //     src: locationIcon,
    //     scale: 0.07,
    //     anchor: [0.51, 1]
    //   }),
    //   text: new Text({
    //     text: coord.join(','),
    //     font: '12px Calibri,sans-serif',
    //     fill: new Fill({
    //       color: '#000',
    //     }),
    //     stroke: new Stroke({
    //       color: '#fff',
    //       width: 3,
    //     }),
    //   })
    // }))

    // // 线要素
    // const feature = new Feature({
    //   geometry: new LineString([coord, [coord[0] + 1, coord[1] + 1]]),
    //   name: '单击线',
    // })

    // 面要素
    const feature = new Feature({
      geometry: new Polygon([[
        [coord[0] - 0.8, coord[1] - 0.8],
        [coord[0] - 0.8, coord[1] + 0.8],
        [coord[0] + 0.8, coord[1] + 0.8],
        [coord[0] + 0.8, coord[1] - 0.8],
        [coord[0] - 0.8, coord[1] - 0.8],
      ]])
    })


    vectorSource.addFeature(feature)
  });

  return vectorLayer
}

/**
 * 地图双击事件
 */
const addDBClick = () => {
  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
  })

  map.addLayer(vectorLayer)

  map.on("dblclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的点

    const feature = new Feature({
      geometry: new Point(coord),
      name: '双击点',
    })  // 点要素

    // 在 OpenLayers 中,样式需要通过 feature.setStyle() 
    // 方法或图层的样式配置来设置而不是作为要素的属性。
    feature.setStyle(new Style({
      image: new Circle({
        radius: 7,
        fill: new Fill({
          color: '#ffcc33',
        }),
        stroke: new Stroke({
          color: '#000',
          width: 2,
        }),
      })
    }))

    vectorSource.addFeature(feature)  // 点要素

    e.preventDefault(); // 阻止浏览器默认行为
    e.stopPropagation(); // 阻止事件冒泡
  });

  return vectorLayer
}

示例:

<template>
  <div id="mapContainer"></div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
// 引入所需模块
import { Map, View } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
// 引入样式
import { Style, Fill, Stroke, Circle, Icon, Text } from "ol/style";
// 引入图标
import locationIcon from '../assets/location.png';

// 引入要素
import Feature from 'ol/Feature.js';
// 引入点要素
import Point from 'ol/geom/Point.js';
// 引入面要素
import Polygon from "ol/geom/Polygon";
// 引入线要素
import LineString from "ol/geom/LineString";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;


/**
 * 地图点击事件
 */
const addSingleClick = () => {

  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: new Style({
      stroke: new Stroke({
        color: 'rgba(0, 0, 255, 1.0)',
        width: 2,
        lineDash: [10, 10]
      }),
      fill: new Fill({
        color: 'rgba(0, 0, 255, 0.2)'
      }),
      image: new Circle({
        radius: 7,
        fill: new Fill({
          color: '#ffcc33',
        }),
        stroke: new Stroke({
          color: '#ea679a',
          width: 2,
        }),
      })
    })
  })

  map.addLayer(vectorLayer)

  map.on("singleclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的要素

    // // 点要素
    // const feature = new Feature({
    //   geometry: new Point(coord),
    //   name: '单击点',
    // })

    // 图片 icon 点, 以及文字
    // const feature = new Feature({
    //   geometry: new Point(coord),
    //   name: '单击点',
    // })

    // // 在 OpenLayers 中,样式需要通过 feature.setStyle() 
    // // 方法或图层的样式配置来设置而不是作为要素的属性。
    // feature.setStyle(new Style({
    //   image: new Icon({
    //     src: locationIcon,
    //     scale: 0.07,
    //     anchor: [0.51, 1]
    //   }),
    //   text: new Text({
    //     text: coord.join(','),
    //     font: '12px Calibri,sans-serif',
    //     fill: new Fill({
    //       color: '#000',
    //     }),
    //     stroke: new Stroke({
    //       color: '#fff',
    //       width: 3,
    //     }),
    //   })
    // }))

    // // 线要素
    // const feature = new Feature({
    //   geometry: new LineString([coord, [coord[0] + 1, coord[1] + 1]]),
    //   name: '单击线',
    // })

    // 面要素
    const feature = new Feature({
      geometry: new Polygon([[
        [coord[0] - 0.8, coord[1] - 0.8],
        [coord[0] - 0.8, coord[1] + 0.8],
        [coord[0] + 0.8, coord[1] + 0.8],
        [coord[0] + 0.8, coord[1] - 0.8],
        [coord[0] - 0.8, coord[1] - 0.8],
      ]])
    })


    vectorSource.addFeature(feature)
  });

  return vectorLayer
}

/**
 * 地图双击事件
 */
const addDBClick = () => {
  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
  })

  map.addLayer(vectorLayer)

  map.on("dblclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的点

    const feature = new Feature({
      geometry: new Point(coord),
      name: '双击点',
    })  // 点要素

    // 在 OpenLayers 中,样式需要通过 feature.setStyle() 
    // 方法或图层的样式配置来设置而不是作为要素的属性。
    feature.setStyle(new Style({
      image: new Circle({
        radius: 7,
        fill: new Fill({
          color: '#ffcc33',
        }),
        stroke: new Stroke({
          color: '#000',
          width: 2,
        }),
      })
    }))

    vectorSource.addFeature(feature)  // 点要素

    e.preventDefault(); // 阻止浏览器默认行为
    e.stopPropagation(); // 阻止事件冒泡
  });

  return vectorLayer
}

onMounted(() => {
  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
  });

  addSingleClick();
  addDBClick();
});

onUnmounted(() => {
  // 清理地图资源
  if (map) {
    map.setTarget(null);
    map = null;
  }
});


// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}
</style>

动态绘制的要素 (Feature) 的事件 (如hover时显示弹窗)

使用说明

/**
 * 地图点击事件, 使用 Select 交互来实现悬停效果, 在指定图层上监听鼠标移动事件, 减少资源消耗
 */
const addSingleClick = () => {

  const style = new Style({
    image: new Icon({
      src: locationIcon,
      scale: 0.07,
      anchor: [0.51, 1]
    }),
  })

  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: style,
  })

  map.addLayer(vectorLayer)

  map.on("singleclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的要素

    // 图片 icon 点, 以及文字
    const feature = new Feature({
      geometry: new Point(coord),
      name: '单击点',
    })

    style.setText(new Text({
      text: coord.join(','),
      font: '12px Calibri,sans-serif',
      fill: new Fill({
        color: '#000',
      }),
      stroke: new Stroke({
        color: '#fff',
        width: 3,
      }),
    }))

    // 在 OpenLayers 中,样式需要通过 feature.setStyle()
    // 方法或图层的样式配置来设置而不是作为要素的属性。
    feature.setStyle(style)

    vectorSource.addFeature(feature)
  });

  // 使用 Select 交互来实现悬停效果, 在指定图层上监听鼠标移动事件, 减少资源消耗
  const select = new Select({
    condition: pointerMove, // 触发条件为鼠标移动
    hitTolerance: 5,
    layers: [vectorLayer], // 指定目标图层
    // style: style, // 悬停时的样式
  });

  select.on('select', (event) => {
    if (event.selected.length > 0) {
      const feature = event.selected[0];
      // 处理悬停逻辑
      if (!feature) return

      console.log(feature.get('name'));
      // 获取要素的坐标
      const coord = feature.getGeometry().getCoordinates();

      // 设置弹窗的位置
      popup.setPosition(coord);

      const popupElement = document.getElementById("popup-content");
      // 设置弹窗的内容
      popupElement.innerHTML = `<p>信息: ${feature.get('name')}</p>
          <p>经纬度: [${coord.join(', ')}]</p>`;

      feature.setStyle(style);  // 设置样式

      // 添加文本, 这种用法只能在 feature.setStyle 设置了 setStyle 之后用
      feature.getStyle().setText(new Text({
        text: coord.join(','),
        font: '12px Calibri,sans-serif',
        fill: new Fill({
          color: '#000',
        }),
        stroke: new Stroke({
          color: '#fff',
          width: 3,
        }),
      }))

    } else {
      // 没有悬停时的逻辑

popup.setPosition(undefined);
    }
  });

  map.addInteraction(select);

  return vectorLayer
}



/**
 * 地图点击事件, 监听 map, 然后在查找要素
 */
const addSingleClick = () => {
  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
  })

  map.addLayer(vectorLayer)

  map.on("singleclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的要素

    // 图片 icon 点, 以及文字
    const feature = new Feature({
      geometry: new Point(coord),
      name: '单击点',
    })

    // 在 OpenLayers 中,样式需要通过 feature.setStyle()
    // 方法或图层的样式配置来设置而不是作为要素的属性。
    feature.setStyle(new Style({
      image: new Icon({
        src: locationIcon,
        scale: 0.07,
        anchor: [0.51, 1]
      }),
      text: new Text({
        text: coord.join(','),
        font: '12px Calibri,sans-serif',
        fill: new Fill({
          color: '#000',
        }),
        stroke: new Stroke({
          color: '#fff',
          width: 3,
        }),
      })
    }))
    vectorSource.addFeature(feature)
  });

  map.on('pointermove', (e) => {
    if (e.dragging) return; // 拖动时不处理

    // 鼠标移动时,获取鼠标所在的要素
    const feature = map.forEachFeatureAtPixel(
      e.pixel,
      (feature) => {
        return feature;
      },
      {
        // hitTolerance: 5,
        layerFilter: (layer) => {
          return layer === vectorLayer;
        },
      }
    );

    // 如果有要素,显示要素的名称
    if (feature) {
      console.log(feature.get('name'))
      // 获取要素的坐标
      const coord = feature.getGeometry().getCoordinates();

      // 设置弹窗的位置
      popup.setPosition(coord);

      const popupElement = document.getElementById("popup-content");
      // 设置弹窗的内容
      popupElement.innerHTML = `<p>信息: ${feature.get('name')}</p>
        <p>经纬度: [${coord.join(', ')}]</p>`;
    } else {
      // 如果没有要素,隐藏弹窗
      popup.setPosition(undefined);
      // map.un('pointermove', move)
    }
  })

  return vectorLayer
}

示例:

<template>
  <div id="mapContainer"></div>

  <div id="popup" class="ol-popup">
    <a href="#" id="popup-closer" class="ol-popup-closer"></a>
    <div id="popup-content"></div>
  </div>
</template>

<script setup>
import { onMounted, ref, onUnmounted } from "vue";
// 引入所需模块
import { Map, View, Overlay } from "ol";
import TileLayer from "ol/layer/Tile";
import XYZ from "ol/source/XYZ";
// 设置坐标系
import proj4 from "proj4";
import { register } from "ol/proj/proj4";

// 引入矢量图层
import VectorLayer from "ol/layer/Vector";
import VectorSource from "ol/source/Vector";
// 引入样式
import { Style, Fill, Stroke, Circle, Icon, Text } from "ol/style";
// 引入图标
import locationIcon from '../assets/location.png';

// 引入要素
import Feature from 'ol/Feature.js';
// 引入点要素
import Point from 'ol/geom/Point.js';
// 引入选择要素
import Select from "ol/interaction/Select";
// 引入鼠标移动事件
import { pointerMove } from "ol/events/condition";

// 定义EPSG:4490(中国大地2000坐标系)
proj4.defs("EPSG:4490", "+proj=longlat +ellps=GRS80 +no_defs");
register(proj4);

let map = null;
let popup = null;


/**
 * 地图点击事件, 监听 map, 然后在查找要素
 */
// const addSingleClick = () => {
//   const vectorSource = new VectorSource()
//   const vectorLayer = new VectorLayer({
//     source: vectorSource,
//   })

//   map.addLayer(vectorLayer)

//   map.on("singleclick", (e) => {
//     const coord = e.coordinate; // 经纬度坐标

//     console.log(coord);

//     vectorSource.clear(); // 清空之前的要素

//     // 图片 icon 点, 以及文字
//     const feature = new Feature({
//       geometry: new Point(coord),
//       name: '单击点',
//     })

//     // 在 OpenLayers 中,样式需要通过 feature.setStyle()
//     // 方法或图层的样式配置来设置而不是作为要素的属性。
//     feature.setStyle(new Style({
//       image: new Icon({
//         src: locationIcon,
//         scale: 0.07,
//         anchor: [0.51, 1]
//       }),
//       text: new Text({
//         text: coord.join(','),
//         font: '12px Calibri,sans-serif',
//         fill: new Fill({
//           color: '#000',
//         }),
//         stroke: new Stroke({
//           color: '#fff',
//           width: 3,
//         }),
//       })
//     }))
//     vectorSource.addFeature(feature)
//   });

//   map.on('pointermove', (e) => {
//     if (e.dragging) return; // 拖动时不处理

//     // 鼠标移动时,获取鼠标所在的要素
//     const feature = map.forEachFeatureAtPixel(
//       e.pixel,
//       (feature) => {
//         return feature;
//       },
//       {
//         // hitTolerance: 5,
//         layerFilter: (layer) => {
//           return layer === vectorLayer;
//         },
//       }
//     );

//     // 如果有要素,显示要素的名称
//     if (feature) {
//       console.log(feature.get('name'))
//       // 获取要素的坐标
//       const coord = feature.getGeometry().getCoordinates();

//       // 设置弹窗的位置
//       popup.setPosition(coord);

//       const popupElement = document.getElementById("popup-content");
//       // 设置弹窗的内容
//       popupElement.innerHTML = `<p>信息: ${feature.get('name')}</p>
//         <p>经纬度: [${coord.join(', ')}]</p>`;
//     } else {
//       // 如果没有要素,隐藏弹窗
//       popup.setPosition(undefined);
//       // map.un('pointermove', move)
//     }
//   })

//   return vectorLayer
// }


/**
 * 地图点击事件, 使用 Select 交互来实现悬停效果, 在指定图层上监听鼠标移动事件, 减少资源消耗
 */
const addSingleClick = () => {

  const style = new Style({
    image: new Icon({
      src: locationIcon,
      scale: 0.07,
      anchor: [0.51, 1]
    }),
  })

  const vectorSource = new VectorSource()
  const vectorLayer = new VectorLayer({
    source: vectorSource,
    style: style,
  })

  map.addLayer(vectorLayer)

  map.on("singleclick", (e) => {
    const coord = e.coordinate; // 经纬度坐标

    console.log(coord);

    vectorSource.clear(); // 清空之前的要素

    // 图片 icon 点, 以及文字
    const feature = new Feature({
      geometry: new Point(coord),
      name: '单击点',
    })

    style.setText(new Text({
      text: coord.join(','),
      font: '12px Calibri,sans-serif',
      fill: new Fill({
        color: '#000',
      }),
      stroke: new Stroke({
        color: '#fff',
        width: 3,
      }),
    }))

    // 在 OpenLayers 中,样式需要通过 feature.setStyle()
    // 方法或图层的样式配置来设置而不是作为要素的属性。
    feature.setStyle(style)

    vectorSource.addFeature(feature)
  });

  // 使用 Select 交互来实现悬停效果, 在指定图层上监听鼠标移动事件, 减少资源消耗
  const select = new Select({
    condition: pointerMove, // 触发条件为鼠标移动
    hitTolerance: 5,
    layers: [vectorLayer], // 指定目标图层
    // style: style, // 悬停时的样式
  });

  select.on('select', (event) => {
    if (event.selected.length > 0) {
      const feature = event.selected[0];
      // 处理悬停逻辑
      if (!feature) return

      console.log(feature.get('name'));
      // 获取要素的坐标
      const coord = feature.getGeometry().getCoordinates();

      // 设置弹窗的位置
      popup.setPosition(coord);

      const popupElement = document.getElementById("popup-content");
      // 设置弹窗的内容
      popupElement.innerHTML = `<p>信息: ${feature.get('name')}</p>
          <p>经纬度: [${coord.join(', ')}]</p>`;

      feature.setStyle(style);  // 设置样式

      // 添加文本, 这种用法只能在 feature.setStyle 设置了 setStyle 之后用
      feature.getStyle().setText(new Text({
        text: coord.join(','),
        font: '12px Calibri,sans-serif',
        fill: new Fill({
          color: '#000',
        }),
        stroke: new Stroke({
          color: '#fff',
          width: 3,
        }),
      }))

    } else {
      // 没有悬停时的逻辑
      popup.setPosition(undefined);
    }
  });

  map.addInteraction(select);

  return vectorLayer
}


onMounted(() => {

  // 创建弹窗元素
  const popupElement = document.getElementById("popup");
  const popupCloser = document.getElementById("popup-closer");

  // 关闭弹窗事件
  popupCloser.onclick = function () {
    popup.setPosition(undefined);
    popupCloser.blur();
    return false;
  };

  // 初始化弹窗
  popup = new Overlay({
    element: popupElement,
    autoPan: true,
    autoPanAnimation: {
      duration: 250,
    },
  });


  // 初始化地图
  map = new Map({
    target: "mapContainer", // 绑定HTML容器ID
    layers: [baseLayer], // 底图图层
    view: new View({
      center: [119, 31], // 初始中心点(EPSG:3857坐标系,需转换经纬度)
      zoom: 6, // 初始缩放级别
      projection: "EPSG:4490", // 坐标系
    }),
    overlays: [popup], // 添加弹窗
    renderer: 'canvas', // 明确使用Canvas渲染
    rendererOptions: {
      canvas: document.createElement('canvas', { willReadFrequently: true })
    },
  });

  addSingleClick();



  // // 或者获取上下文时设置
  // const ctx = canvas.getContext('2d', { willReadFrequently: true });
});

onUnmounted(() => {
  // 清理地图资源
  if (map) {
    map.setTarget(null);
    map = null;
  }
});


// 创建底图图层(XYZ瓦片)
const baseLayer = new TileLayer({
  source: new XYZ({
    url: "http://wprd0{1-4}.is.autonavi.com/appmaptile?x={x}&y={y}&z={z}&lang=zh_cn&size=1&scl=1&style=7",
  }),
});
</script>

<style scoped>
#mapContainer {
  box-sizing: border-box;
  width: 100vw;
  height: 100vh;
}

.ol-popup {
  position: absolute;
  background-color: white;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.2);
  padding: 15px;
  border-radius: 10px;
  border: 1px solid #cccccc;
  bottom: 70px;
  left: -50px;
  min-width: 350px;
}

.ol-popup:after,
.ol-popup:before {
  top: 100%;
  border: solid transparent;
  content: " ";
  height: 0;
  width: 0;
  position: absolute;
  pointer-events: none;
}

.ol-popup:after {
  border-top-color: white;
  border-width: 10px;
  left: 48px;
  margin-left: -10px;
}

.ol-popup:before {
  border-top-color: #cccccc;
  border-width: 11px;
  left: 48px;
  margin-left: -11px;
}

.ol-popup-closer {
  text-decoration: none;
  position: absolute;
  top: 2px;
  right: 8px;
}

.ol-popup-closer:after {
  content: "✖";
}
</style>

四下皆无人