当前位置: 首页 > news >正文

OpenLayers 综合案例-信息窗体-弹窗

看过的知识不等于学会。唯有用心总结、系统记录,并通过温故知新反复实践,才能真正掌握一二
作为一名摸爬滚打三年的前端开发,开源社区给了我饭碗,我也将所学的知识体系回馈给大家,助你少走弯路!
OpenLayers、Leaflet 快速入门 ,每周保持更新 2 个案例
Cesium 快速入门,每周保持更新 4 个案例

OpenLayers 综合案例-信息窗体-弹窗

Vue 3 + OpenLayers 实现的 WebGIS 应用提供了完整的信息窗体-弹窗功能

主要功能

  1. 地图点击弹窗(显示坐标、缩放、分辨率)
  2. 双击添加点标记(弹窗可删除)
  3. 框选(Ctrl+拖动)弹窗显示包络框
  4. 要素删除功能(单个删除、全部删除)
  5. 右键菜单(放大、缩小、定位、关闭)
    在这里插入图片描述

MP4效果动画链接地址

技术栈

该环境下代码即拿即用

Vue 3.5.13+
OpenLayers 10.5.0+
Vite 6.3.5+
<template><div class="ol-tutorial-demo-container"><div ref="mapContainer" class="map"></div><!-- 状态栏 --><div class="statusbar"><span>坐标: {{ mouseCoord }}</span><span>缩放: {{ zoom }}</span><span>旋转: {{ rotation }}°</span><span>分辨率: {{ resolution }}</span></div><!-- 框选包络框弹窗 --><div v-if="boxInfo" class="popup" :style="boxPopupStyle"><div><strong>框选范围</strong></div><div>西南: {{ boxInfo.sw }}</div><div>东北: {{ boxInfo.ne }}</div><button class="close-btn" @click="boxInfo = null">关闭</button></div><!-- 点标记弹窗 --><div v-if="pointPopup" class="popup" :style="pointPopupStyle"><div><strong>点标记</strong></div><div>经度: {{ pointPopup.lon }}</div><div>纬度: {{ pointPopup.lat }}</div><button class="close-btn" @click="removePoint(pointPopup.id)">删除</button><button class="close-btn" @click="pointPopup = null">关闭</button></div><!-- 地图点击弹窗 --><div v-if="mapPopup" class="popup" :style="mapPopupStyle"><div><strong>地图信息</strong></div><div>经度: {{ mapPopup.lon }}</div><div>纬度: {{ mapPopup.lat }}</div><div>缩放: {{ mapPopup.zoom }}</div><div>分辨率: {{ mapPopup.resolution }}</div><button class="close-btn" @click="mapPopup = null">关闭</button></div><!-- 右键菜单 --><ulv-if="contextMenu.visible"class="context-menu":style="contextMenu.style"><li @click="zoomIn">放大一级</li><li @click="zoomOut">缩小一级</li><li @click="centerBeijing">定位到北京</li><li @click="closeContextMenu">关闭</li></ul></div>
</template><script setup>
import { ref, onMounted, onUnmounted, nextTick } from "vue";
import Map from "ol/Map";
import View from "ol/View";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer";
import { XYZ, Vector as VectorSource } from "ol/source";
import { Point } from "ol/geom";
import Feature from "ol/Feature";
import { Style, Stroke, Circle, Fill } from "ol/style";
import { defaults as defaultControls, FullScreen, ScaleLine } from "ol/control";
import { defaults as defaultInteractions } from "ol/interaction/defaults";
import { fromLonLat, toLonLat } from "ol/proj";
import DragBox from "ol/interaction/DragBox";
import { platformModifierKeyOnly } from "ol/events/condition";
import "ol/ol.css";const map = ref(null);
const mapContainer = ref(null);
const vectorSource = ref(null);
const pointLayer = ref(null);
const dragBox = ref(null);// 状态栏
const mouseCoord = ref("");
const zoom = ref(0);
const rotation = ref(0);
const resolution = ref(0);// 地图弹窗
const mapPopup = ref(null);
const mapPopupStyle = ref({});// 点标记
const points = ref([]); // {id, lon, lat, feature}
const pointPopup = ref(null);
const pointPopupStyle = ref({});
let pointId = 1;
function addPoint(lon, lat) {const feature = new Feature({ geometry: new Point(fromLonLat([lon, lat])) });feature.setStyle(new Style({image: new Circle({radius: 8,fill: new Fill({ color: "#ff5722" }),stroke: new Stroke({ color: "#fff", width: 2 }),}),}));pointLayer.value.getSource().addFeature(feature);points.value.push({ id: pointId++, lon, lat, feature });
}
function removePoint(id) {const idx = points.value.findIndex((p) => p.id === id);if (idx !== -1) {pointLayer.value.getSource().removeFeature(points.value[idx].feature);points.value.splice(idx, 1);pointPopup.value = null;}
}// 框选
const boxInfo = ref(null);
const boxPopupStyle = ref({});// 右键菜单
const contextMenu = ref({ visible: false, style: {}, pixel: null });
function closeContextMenu() {contextMenu.value.visible = false;
}
function zoomIn() {map.value.getView().setZoom(map.value.getView().getZoom() + 1);closeContextMenu();
}
function zoomOut() {map.value.getView().setZoom(map.value.getView().getZoom() - 1);closeContextMenu();
}
function centerBeijing() {map.value.getView().setCenter(fromLonLat([116.4, 39.9]));map.value.getView().setZoom(12);closeContextMenu();
}onMounted(() => {// 初始化图层const baseLayer = new TileLayer({source: new XYZ({url: "https://webrd04.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}",}),});vectorSource.value = new VectorSource();pointLayer.value = new VectorLayer({ source: new VectorSource() });// 初始化地图map.value = new Map({target: mapContainer.value,layers: [baseLayer, pointLayer.value],view: new View({ center: fromLonLat([116.4, 39.9]), zoom: 12 }),controls: defaultControls().extend([new FullScreen(), new ScaleLine()]),interactions: defaultInteractions({doubleClickZoom: false, // 禁用双击缩放}),});// 状态栏zoom.value = map.value.getView().getZoom();rotation.value = ((map.value.getView().getRotation() * 180) /Math.PI).toFixed(1);resolution.value = map.value.getView().getResolution().toFixed(2);map.value.on("pointermove", (evt) => {const [lon, lat] = toLonLat(evt.coordinate);mouseCoord.value = `${lon.toFixed(6)}, ${lat.toFixed(6)}`;});map.value.getView().on("change:rotation", () => {rotation.value = ((map.value.getView().getRotation() * 180) /Math.PI).toFixed(1);});map.value.getView().on("change:resolution", () => {resolution.value = map.value.getView().getResolution().toFixed(2);zoom.value = map.value.getView().getZoom().toFixed(2);});// 单击弹窗map.value.on("singleclick", (evt) => {closeContextMenu();// 检查点标记const feature = map.value.forEachFeatureAtPixel(evt.pixel, (f) => f);if (feature &&pointLayer.value.getSource().getFeatures().includes(feature)) {// 点标记弹窗const [lon, lat] = toLonLat(feature.getGeometry().getCoordinates());pointPopup.value = {id: points.value.find((p) => p.feature === feature).id,lon: lon.toFixed(6),lat: lat.toFixed(6),};pointPopupStyle.value = {left: evt.pixel[0] + 20 + "px",top: evt.pixel[1] + 20 + "px",};return;}// 地图弹窗const [lon, lat] = toLonLat(evt.coordinate);mapPopup.value = {lon: lon.toFixed(6),lat: lat.toFixed(6),zoom: zoom.value,resolution: resolution.value,};mapPopupStyle.value = {left: evt.pixel[0] + 20 + "px",top: evt.pixel[1] + 20 + "px",};});// 双击添加点标记map.value.on("dblclick", (evt) => {const [lon, lat] = toLonLat(evt.coordinate);addPoint(lon, lat);// 自动弹窗nextTick(() => {pointPopup.value = {id: pointId - 1,lon: lon.toFixed(6),lat: lat.toFixed(6),};pointPopupStyle.value = {left: evt.pixel[0] + 20 + "px",top: evt.pixel[1] + 20 + "px",};});evt.preventDefault();});// 框选dragBox.value = new DragBox({ condition: platformModifierKeyOnly });map.value.addInteraction(dragBox.value);dragBox.value.on("boxend", (e) => {const extent = dragBox.value.getGeometry().getExtent();const sw = toLonLat([extent[0], extent[1]]);const ne = toLonLat([extent[2], extent[3]]);boxInfo.value = {sw: `${sw[0].toFixed(6)}, ${sw[1].toFixed(6)}`,ne: `${ne[0].toFixed(6)}, ${ne[1].toFixed(6)}`,};boxPopupStyle.value = {left: e.target.box_.endPixel_[0] + 20 + "px",top: e.target.box_.endPixel_[1] + 20 + "px",};});// 右键菜单mapContainer.value.addEventListener("contextmenu", (e) => {e.preventDefault();contextMenu.value = {visible: true,style: { left: e.clientX + "px", top: e.clientY + "px" },};});
});onUnmounted(() => {if (map.value) map.value.dispose();
});
</script><style scoped>
.ol-tutorial-demo-container {position: relative;width: 100vw;height: 100vh;overflow: hidden;font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
}
.map {width: 100%;height: 100%;
}
.statusbar {position: absolute;left: 20px;bottom: 20px;background: rgba(255, 255, 255, 0.95);border-radius: 8px;padding: 10px 18px;font-size: 1rem;color: #1a237e;display: flex;gap: 18px;z-index: 10;box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}
.popup {position: absolute;min-width: 180px;background: #fffbe7;border: 1px solid #ffe082;border-radius: 8px;padding: 15px 20px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);z-index: 20;color: #795548;
}
.close-btn {margin-top: 10px;background: #ffc107;border: none;border-radius: 6px;padding: 6px 12px;color: #fff;font-weight: 600;cursor: pointer;
}
.close-btn:hover {background: #ff9800;
}
.context-menu {position: absolute;z-index: 30;background: #fff;border: 1px solid #eee;border-radius: 8px;box-shadow: 0 8px 32px rgba(0, 0, 0, 0.18);list-style: none;padding: 0;margin: 0;min-width: 140px;font-size: 1rem;
}
.context-menu li {padding: 12px 18px;cursor: pointer;color: #1976d2;border-bottom: 1px solid #f5f5f5;transition: background 0.2s;
}
.context-menu li:last-child {border-bottom: none;
}
.context-menu li:hover {background: #e3f2fd;
}
</style>
http://www.xdnf.cn/news/1202815.html

相关文章:

  • 对于ui=f(state)的理解(react)
  • python毕业设计案例:基于python django的抖音数据分析与可视化系统,可视化有echarts,算法包括lstm+朴素贝叶斯算法
  • 【Datawhale夏令营】端侧Agent开发实践
  • 【Unity笔记】Unity Camera.cullingMask 使用指南:Layer 精准控制、XR 多视图与性能提升
  • [机缘参悟-236]:通过AI人工神经网络理解人的思维特征:惯性思维、路径依赖、适应性、不同场合不同言行、经验、概率、常规与特殊情形(正态分布)、环境适应性
  • 【linux】md5文件相似校验介绍与实战示例
  • 零基础学习性能测试第九章:全链路追踪-项目实操
  • 设计模式(十七)行为型:迭代器模式详解
  • react前端样式如何给元素设置高度自适应
  • debian系统分卷是不会影响系统启动速度?
  • 内存分页机制分析在海外VPS系统的测试流程
  • C语言:20250728学习(指针)
  • 如何给电脑换个ip地址?电脑换ip几种方法
  • 从零开始的云计算生活——第三十七天,跬步千里,ansible之playbook
  • linux_centos7安装jdk8_采用jdk安装包安装
  • 电脑出现英文字母开不了机怎么办 原因与修复方法
  • 【Java EE】多线程-初阶-线程的状态
  • 云原生作业(haproxy)
  • 设计模式十二:门面模式 (FaçadePattern)
  • C++11之lambda及包装器
  • java设计模式 -【责任链模式】
  • 【智慧物联网平台】编译jar环境 Linux 系统Maven 安装——仙盟创梦IDE
  • RK3568基于mpp实现硬解码(二):FFmpeg + mpp实现ipc摄像头图像解码
  • C++---初始化列表(initializer_list)
  • maven 打包报错 process terminated
  • 数据库原理
  • MCP资源管理深度实践:动态数据源集成方案
  • 终结集成乱局:模型上下文协议(MCP)如何重构AI工具生态?
  • 深入探索Linux:忙碌的车间“进程”间通信
  • 四、计算机组成原理——第6章:总线