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 图层显示信息,我们需要:
-
监听地图的点击事件
-
检查点击位置是否在某个 GeoJSON 要素上
-
如果是,则获取该要素的属性信息
-
显示信息(可以使用弹窗、悬浮框等方式)
使用说明:
/**
* 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>