天地图实战:手把手教你用原生JS实现多边形绘制与顶点坐标获取(附完整代码)
天地图原生开发实战从多边形绘制到交互功能全解析在GIS应用开发中地图交互功能往往是核心需求之一。当我们需要让用户在地图上手动划定区域时——无论是物流配送范围、巡检区域还是服务覆盖范围——多边形绘制与坐标获取都是必不可少的技能。本文将带你深入探索如何不依赖任何前端框架仅用原生JavaScript实现天地图的多边形绘制、顶点坐标获取以及完整的交互功能链。1. 环境准备与基础配置在开始编码前我们需要确保开发环境正确配置。天地图作为国内主流的地图服务之一其API设计与百度地图、高德地图等存在显著差异这也是许多开发者初次接触时容易困惑的地方。首先在HTML中引入天地图JavaScript API。最新版本的API通常通过CDN引入script typetext/javascript srchttps://api.tianditu.gov.cn/api?v4.0tk您的密钥/script提示申请天地图开发者密钥时注意选择适合的服务类型。Web端应用通常选择浏览器端而非服务端。初始化地图实例时建议设置合理的初始视图和控件const map new T.Map(map-container, { projection: EPSG:4326, // 使用WGS84坐标系 center: new T.LngLat(116.404, 39.915), // 默认中心点坐标 zoom: 12 // 初始缩放级别 }); // 添加基本控件 map.addControl(new T.Control.Zoom()); map.addControl(new T.Control.OverView());2. 多边形绘制工具的实现天地图提供了专门的T.PolygonTool类来处理多边形绘制相比直接操作鼠标事件使用官方工具类能大幅减少代码量并提高稳定性。2.1 初始化绘制工具创建多边形绘制工具时可以通过配置对象自定义绘制样式const polygonTool new T.PolygonTool(map, { showLabel: true, // 显示面积标签 color: #3388ff, // 边线颜色 weight: 3, // 边线宽度 opacity: 0.8, // 边线透明度 fillColor: #3388ff, // 填充颜色 fillOpacity: 0.3 // 填充透明度 });启动绘制工具只需调用open()方法document.getElementById(draw-btn).addEventListener(click, () { polygonTool.open(); });2.2 捕获绘制完成事件绘制完成后我们需要获取多边形的顶点坐标。天地图会在draw事件中返回完整的坐标信息polygonTool.addEventListener(draw, (e) { const vertices e.currentLnglats; // 获取顶点坐标数组 console.log(多边形顶点坐标:, vertices); // 坐标格式转换示例转为{lng, lat}对象数组 const formattedCoords vertices.map(coord ({ lng: coord.getLng(), lat: coord.getLat() })); // 可以在此处将坐标发送到后端或进行其他处理 savePolygonToDatabase(formattedCoords); });注意currentLnglats返回的是T.LngLat对象数组直接使用前可能需要格式转换。3. 高级交互功能实现基础绘制功能实现后我们通常需要添加更多交互元素来提升用户体验。下面实现双击多边形弹出操作菜单的功能。3.1 自定义菜单设计与样式首先在HTML中添加菜单结构这里使用纯CSS实现而非依赖UI框架div idpolygon-context-menu classcontext-menu ul li idmenu-edit编辑多边形/li li idmenu-delete删除多边形/li li idmenu-cancel取消/li /ul /div对应的CSS样式.context-menu { position: absolute; z-index: 1000; background: white; border: 1px solid #ddd; border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.2); display: none; } .context-menu ul { list-style: none; padding: 0; margin: 0; } .context-menu li { padding: 8px 15px; cursor: pointer; font-size: 14px; } .context-menu li:hover { background-color: #f5f5f5; }3.2 实现双击交互逻辑为绘制完成的多边形添加双击事件监听let currentPolygon null; // 存储当前操作的多边形 polygonTool.addEventListener(draw, (e) { const polygon e.currentPolygon; currentPolygon polygon; // 保存引用 // 添加双击事件监听 polygon.addEventListener(dblclick, (evt) { showContextMenu(evt); }); }); function showContextMenu(event) { const menu document.getElementById(polygon-context-menu); const mapContainer document.getElementById(map-container); // 计算菜单位置 const containerRect mapContainer.getBoundingClientRect(); const x event.clientX - containerRect.left; const y event.clientY - containerRect.top; menu.style.left ${x}px; menu.style.top ${y}px; menu.style.display block; // 点击其他地方隐藏菜单 document.addEventListener(click, hideContextMenu); }3.3 实现菜单功能为菜单项添加具体功能document.getElementById(menu-delete).addEventListener(click, () { if (currentPolygon) { map.removeOverLay(currentPolygon); currentPolygon null; } hideContextMenu(); }); document.getElementById(menu-edit).addEventListener(click, () { if (currentPolygon) { // 进入编辑模式 polygonTool.edit(currentPolygon); } hideContextMenu(); }); function hideContextMenu() { const menu document.getElementById(polygon-context-menu); menu.style.display none; document.removeEventListener(click, hideContextMenu); }4. 性能优化与错误处理在实际项目中我们需要考虑更多边界情况和性能问题。4.1 内存管理长时间使用地图应用可能导致内存泄漏特别是在频繁创建和删除覆盖物时// 删除多边形时的完整处理 function removePolygon(polygon) { try { // 移除所有事件监听器 polygon.off(dblclick); // 从地图移除 map.removeOverLay(polygon); // 如果是当前操作的多边形清除引用 if (currentPolygon polygon) { currentPolygon null; } } catch (error) { console.error(删除多边形时出错:, error); } }4.2 坐标验证从用户绘制的多边形获取坐标后应该进行基本验证function validatePolygon(coords) { if (!Array.isArray(coords) || coords.length 3) { throw new Error(多边形至少需要3个顶点); } // 检查坐标是否有效 coords.forEach(coord { if (coord.lng -180 || coord.lng 180 || coord.lat -90 || coord.lat 90) { throw new Error(无效坐标值: ${coord.lng}, ${coord.lat}); } }); // 检查多边形是否闭合首尾坐标相同 const first coords[0]; const last coords[coords.length - 1]; if (first.lng ! last.lng || first.lat ! last.lat) { coords.push({...first}); // 自动闭合多边形 } return coords; }4.3 大数量处理当需要同时显示大量多边形时可以采用以下优化策略// 使用图层组管理多个多边形 const polygonLayer new T.LayerGroup(); map.addLayer(polygonLayer); // 添加多边形到图层组 function addPolygonToLayer(coords) { const polygon new T.Polygon(coords.map(c new T.LngLat(c.lng, c.lat)), { color: #3388ff, weight: 2, fillOpacity: 0.3 }); polygonLayer.addLayer(polygon); return polygon; } // 清空所有多边形 function clearAllPolygons() { polygonLayer.clearLayers(); }5. 实际应用扩展掌握了基础功能后我们可以进一步扩展应用场景。5.1 多边形编辑功能天地图的PolygonTool提供了内置的编辑功能function enableEditMode(polygon) { polygonTool.close(); // 先关闭绘制模式 // 进入编辑模式 polygonTool.edit(polygon); // 监听编辑完成事件 polygonTool.on(edit, (e) { const newCoords e.currentLnglats; console.log(编辑后的坐标:, newCoords); }); }5.2 与后端API集成将多边形数据保存到后端时通常需要适当的序列化async function savePolygonToServer(coords, name) { try { const response await fetch(/api/polygons, { method: POST, headers: { Content-Type: application/json }, body: JSON.stringify({ name: name, coordinates: coords, type: Polygon }) }); if (!response.ok) { throw new Error(保存失败: ${response.statusText}); } return await response.json(); } catch (error) { console.error(保存多边形时出错:, error); throw error; } }5.3 从数据库加载并显示多边形从后端获取数据后重新在地图上渲染async function loadAndDisplayPolygons() { try { const response await fetch(/api/polygons); const polygons await response.json(); polygons.forEach(polygonData { const coords polygonData.coordinates.map(c new T.LngLat(c.lng, c.lat)); const polygon new T.Polygon(coords, { color: #3388ff, weight: 2, fillOpacity: 0.3 }); // 添加双击事件 polygon.on(dblclick, (e) { currentPolygon polygon; showContextMenu(e); }); map.addOverLay(polygon); }); } catch (error) { console.error(加载多边形时出错:, error); } }在项目开发过程中我发现天地图的API虽然功能全面但在事件处理机制上与其他地图服务有所不同。特别是在处理覆盖物交互时需要特别注意事件冒泡和委托的问题。一个实用的技巧是为所有多边形添加唯一标识符这样在处理交互时能够准确定位到具体的多边形实例。