需求:
用户自定义上传一张平面图,然后可以在平面图内标点、绘制面、并且能够弹出相对应点、面的信息,信息可编辑,类似下图:
相关实现技术:leaflet
中文网:Leaflet - 一个交互式地图 JavaScript 库 (leafletjs.cn)
官方网:Leaflet - a JavaScript library for interactive maps (leafletjs.com)
官网相关示例:Overlays - Leaflet - 一个交互式地图 JavaScript 库 (leafletjs.cn)
相关js跟css资源可在npm中下载:leaflet - npm (npmjs.com)
开始编码demo
实现功能如下:加载平面图并默认生成一个点跟面、新增绘制点,面功能、编辑点,面弹出消息功能、删除点面功能、以及设置绘制面的颜色。全部代码如下:
<html><head><link rel="stylesheet" href="./leaflet.css" /><script type="text/javascript" src="./jquery.min.js"></script><script src="./leaflet.js"> </script><script src="./leaflet-src.js"> </script><script src="./leaflet-src.esm.js"> </script><style>#image-map {min-width: 60vw;height: 80vh;border: 1px solid #ccc;margin-bottom: 10px;}#buttons {margin: 10px;}.activeBtn {color: white;background-color: #7070ff;}.btn {margin-right: 10px;padding: 10px;border: none;cursor: pointer;}.btnCol {margin-right: 10px;padding: 10px;border: none;cursor: pointer;}.delete-button {position: absolute;right: 0;}#markers-list,#polygons-list {flex: 1;display: flex;flex-direction: column;align-items: center;}#markers-list>div,#polygons-list>div {position: relative;}#markers-list .childDiv,#polygons-list .childDiv {cursor: pointer;margin: 10px;width: 150px;white-space: nowrap;overflow: hidden;text-overflow: ellipsis;border: 1px solid #eaeaea;border-radius: 50px;padding: 10px;}.allBox {display: flex;justify-content: space-between;}/* .leaflet-important {transform: translate3d(0px, 0px, 0px) !important;} */.leaflet-container {background-color: white;}.leaflet-bottom {display: none;}</style>
</head><body><div id="buttons"><button id="add-marker-button" class="btn">新增标记点</button><button id="draw-area-button" class="btn">绘制区域</button><button id="red" class="btnCol">红色</button><button id="green" class="btnCol activeBtn">绿色</button><button id="orange" class="btnCol">橙色</button></div><div class="allBox"><div id="markers-list"></div><div id="image-map"></div><div id="polygons-list"></div></div><script>$(document).ready(function () {// 切换颜色按钮var DBXcolor = 'green';$("#red").click(function () {DBXcolor = 'red'$(".btnCol.activeBtn").removeClass("activeBtn");$(this).addClass('activeBtn')})$("#green").click(function () {DBXcolor = 'green'$(".btnCol.activeBtn").removeClass("activeBtn");$(this).addClass('activeBtn')})$("#orange").click(function () {DBXcolor = 'orange'$(".btnCol.activeBtn").removeClass("activeBtn");$(this).addClass('activeBtn')})// 创建一个Leaflet地图实例var map = L.map('image-map', {minZoom: 1, // 设置地图的最小缩放级别maxZoom: 4, // 设置地图的最大缩放级别crs: L.CRS.Simple // 使用简单的坐标参考系统,适用于平面图片的映射});// 定义图片的宽度和高度,以及图片的路径let w = 3200,h = 2600,url = './base.png';// 将像素坐标转换为经纬度坐标,并定义图片的边界(西南和东北角)var southWest = map.unproject([0, h], map.getMaxZoom() - 1); // 西南角像素坐标映射为经纬度坐标var northEast = map.unproject([w, 0], map.getMaxZoom() - 1); // 东北角像素坐标映射为经纬度坐标var bounds = new L.LatLngBounds(southWest, northEast); // 创建边界范围// 在地图上添加图片覆盖层,并指定图片的边界范围以及位置L.imageOverlay(url, bounds).addTo(map);map.fitBounds(bounds);// 在地图上添加标记,并绑定弹出窗口,默认情况下弹出窗口是打开的let newMarkerId1 = generateUniqueId('marker');let customIcon = L.icon({iconUrl: 'one.gif',iconSize: [64, 52], // 设置图标大小iconAnchor: [32, 32], // 设置图标的中心点popupAnchor: [0, -32] // 设置弹出窗口的位置});let divStr = `<div><div style="display: flex;"><div>相机名称:</div><div>xxx</div></div><div style="display: flex;"><div>报警地址:</div><div>xxx</div></div><div style="display: flex;"><div>报警类型:</div><div>xxx</div></div><div style="display: flex;"><div>报警次数:</div><div>xxx</div></div></div>`let marker = L.marker([-180, 200], { icon: customIcon, id: newMarkerId1 }).addTo(map).bindPopup(divStr).openPopup();// 添加mouseover事件marker.on('mouseover', function (e) {this.openPopup();});// 添加mouseout事件marker.on('mouseout', function (e) {this.closePopup();});// 在地图上添加一个多边形,并绑定弹出窗口let newPolygonId = generateUniqueId('polygon');L.polygon([[-200, 80],[-250, 80],[-120, 150],[-140, 100]], { id: newPolygonId, color: 'green' }).addTo(map).bindPopup("多边形");function generateUniqueId(prefix) {return prefix + '_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);}// 获取当前地图上所有的 marker 和 polygon 信息function getMapLayersInfo() {let markers = [];let polygons = [];map.eachLayer(function (layer) {if (layer instanceof L.Marker) {let markerInfo = {id: layer.options.id || null,latlng: layer.getLatLng(),popup: layer.getPopup() ? layer.getPopup().getContent() : null};markers.push(markerInfo);} else if (layer instanceof L.Polygon) {let polygonInfo = {id: layer.options.id || null,latlngs: layer.getLatLngs(),popup: layer.getPopup() ? layer.getPopup().getContent() : null};polygons.push(polygonInfo);}});// 遍历并添加markers到HTML中let markersList = $('#markers-list');markersList.empty();$.each(markers, function (index, marker) {let markerElementBox = $('<div></div>');let markerElementChild = $('<div></div>').text(`Marker ID: ${marker.id}`).addClass('childDiv');// 创建删除按钮let deleteButton = $('<button>X</button>').addClass('delete-button');deleteButton.on('click', function (event) {event.stopPropagation();handleMarkerDoubleClick(marker.id);});// 将删除按钮添加到 markerElementChild 中markerElementBox.append(deleteButton);markerElementBox.append(markerElementChild);markerElementChild.on('click', function () {handleMarkerClick(marker.id);});markersList.append(markerElementBox);});// 遍历并添加polygons到HTML中let polygonsList = $('#polygons-list');polygonsList.empty();$.each(polygons, function (index, polygon) {let polygonElementBox = $('<div></div>');let polygonElementChild = $('<div></div>').text(`Polygon ID: ${polygon.id}`).addClass('childDiv');// 创建删除按钮let deleteButton = $('<button>X</button>').addClass('delete-button');deleteButton.on('click', function (event) {event.stopPropagation();handlePolygonDoubleClick(polygon.id);});// 将删除按钮添加到 polygonElementChild 中polygonElementBox.append(deleteButton);polygonElementBox.append(polygonElementChild);polygonElementChild.on('click', function () {handlePolygonClick(polygon.id);});polygonsList.append(polygonElementBox);});console.log("Markers:", markers);console.log("Polygons:", polygons);}// 处理标注点双击事件function handleMarkerDoubleClick(markerId) {console.log("Double clicked Marker ID:", markerId);// 进行其他操作map.eachLayer(function (layer) {// 如果图层是标记且具有与目标ID匹配的自定义IDif (layer instanceof L.Marker && layer.options.id === markerId) {map.removeLayer(layer);getMapLayersInfo()}});}// 处理多边形双击事件function handlePolygonDoubleClick(polygonId) {console.log("Double clicked Polygon ID:", polygonId);// 进行其他操作,如放大多边形或显示相关信息map.eachLayer(function (layer) {// 如果图层是标记且具有与目标ID匹配的自定义IDif (layer instanceof L.Polygon && layer.options.id === polygonId) {map.removeLayer(layer);getMapLayersInfo()}});}// 处理点击标记的事件function handleMarkerClick(markerId) {console.log("Clicked Marker ID:", markerId);// 进行其他操作,如高亮标记或显示相关信息// 通过 ID 获取对应的标记对象// 遍历地图上的每个图层map.eachLayer(function (layer) {// 如果图层是标记且具有与目标ID匹配的自定义IDif (layer instanceof L.Marker && layer.options.id === markerId) {let currentPopupContent = layer.getPopup() ? layer.getPopup().getContent() : '';// 修改标记的 bindPopup 值console.log(currentPopupContent);let newPopupContent = prompt(currentPopupContent);if (newPopupContent !== null) {layer.bindPopup(newPopupContent).openPopup();}}});}// 处理点击多边形的事件function handlePolygonClick(polygonId) {console.log("Clicked Polygon ID:", polygonId);// 进行其他操作,如高亮多边形或显示相关信息map.eachLayer(function (layer) {// 如果图层是标记且具有与目标ID匹配的自定义IDif (layer instanceof L.Polygon && layer.options.id === polygonId) {let currentPopupContent = layer.getPopup() ? layer.getPopup().getContent() : '';// 修改标记的 bindPopup 值let newPopupContent = prompt(currentPopupContent);if (newPopupContent !== null) {layer.bindPopup(newPopupContent).openPopup();}}});}// 示例调用获取地图上所有的 marker 和 polygon 信息getMapLayersInfo();// 新增标记点功能let addMarkerMode = false; // 标记是否处于新增标记模式let drawAreaMode = false; // 标记是否处于绘制区域模式let polygon; // 保存当前绘制的多边形let latlngs = []; // 保存多边形顶点的数组$('#add-marker-button').on('click', function () {addMarkerMode = !addMarkerMode; // 切换模式if (addMarkerMode) {$(this).text("取消新增标记点").addClass('activeBtn');// 确保绘制区域模式关闭drawAreaMode = false;$('#draw-area-button').text("绘制区域");map.on('click', onMapClick);} else {$(this).text("新增标记点").removeClass('activeBtn');map.off('click', onMapClick);getMapLayersInfo();}});$('#draw-area-button').on('click', function () {drawAreaMode = !drawAreaMode; // 切换模式if (drawAreaMode) {$(this).text("完成绘制区域").addClass('activeBtn');// 确保新增标记模式关闭addMarkerMode = false;$('#add-marker-button').text("新增标记点");map.on('click', onDrawAreaClick);} else {$(this).text("绘制区域").removeClass('activeBtn');map.off('click', onDrawAreaClick);if (latlngs.length > 2) {let popupContent = prompt("请输入区域的弹出内容:");if (popupContent) {map.removeLayer(polygon);let newPolygonId = generateUniqueId('polygon');L.polygon(latlngs, { id: newPolygonId, color: DBXcolor }).addTo(map).bindPopup(popupContent).openPopup();getMapLayersInfo();} else {map.removeLayer(polygon); // 移除多边形}latlngs = []; // 重置顶点数组} else {alert('不足三个点');}}});function onMapClick(e) {let popupContent = prompt("请输入标记点的弹出内容:");if (popupContent) {let newMarkerId = generateUniqueId('marker');L.marker(e.latlng, { icon: customIcon, id: newMarkerId }).addTo(map).bindPopup(popupContent).openPopup();}}function onDrawAreaClick(e) {latlngs.push(e.latlng); // 添加顶点到数组if (polygon) {map.removeLayer(polygon); // 移除之前的临时多边形}polygon = L.polygon(latlngs, { color: DBXcolor }).addTo(map); // 创建临时多边形}});</script></body></html>
文件夹如下:
效果图如下: