一、文档
vue3
javascript WGS84、GCj02相互转换
天地图官方文档
注册登录然后申请应用key,通过CDN引入
<script src="http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥" type="text/javascript"></script>
二、分析
所谓电子围栏
1、就是在地图商通过经纬度将点标注出来,然后将这些点连起来渲染为一个面
2、然后在此基础上支持编辑即添加删除以及拖拽
3、在每次操作完成之后都对天地图视图重新渲染
三、代码实现
1、在线体验
2、渲染标注点以及面
文档:添加标注点 添加多边形
直接通过坐标点把标注点以及多边形渲染出来
<script setup>import { onMounted, ref } from 'vue';defineOptions({name: ''});const props = defineProps({})const emits = defineEmits(['on-ok']);onMounted(() => {setTimeout(() => {initTMap();}, 1000);});const TMap = ref(null);const primaryColor = ref('#0249f9');const latlngPointSet = ref('30.065455529211373,120.56547777078745;30.05799538979555,120.56624760591018;30.06226195624971,120.58499353379405'); // 围栏坐标点集const addMarkerPos = ref({}); // 当前要手动添加的morKer的坐标位置const fenceAssembly = ref({TMapMarker: [],latlngArr: [],latlngStr: '',polygon: null,})function initTMap() {// 转换坐标const latlngArr = latlngPointSet.value.split(';').map(ele => {const [lat, lng] = ele.split(',');return `${lat},${lng}`});fenceAssembly.value = {TMapMarker: [],polygon: null,latlngArr: latlngArr,latlngStr: latlngArr.join(';'),};// 转换坐标(中心点)const center = new T.LngLat(120.57927905745419, 30.05445160751536);// 初始化地图TMap.value = new T.Map('container');TMap.value.centerAndZoom(center, 15);operateTMap();}// 渲染天地图function operateTMap() {if (!TMap.value) return;// 先清除所有覆盖物TMap.value.clearOverLays();const data = fenceAssembly.value.latlngArr || [];// 添加标注点data.forEach(ele => {const [lat, lng] = ele.split(',');addMarkerPos.value = new T.LngLat(lng, lat)addMarker();})// 创建并添加多边形对象const points = data.map(ele => {const [lat, lng] = ele.split(',');return new T.LngLat(lng, lat);});if (points.length >= 3) { // 确保有足够的点来创建一个多边形fenceAssembly.value.points = pointsfenceAssembly.value.polygon = new T.Polygon(points, {color: primaryColor.value,weight: 3,opacity: 1,fillColor: "#44aaf8",fillOpacity: 0.3});TMap.value.addOverLay(fenceAssembly.value.polygon);}}// 添加标注点function addMarker() {const latLng = addMarkerPos.value;if (!TMap.value || !latLng) return;// 获取当前标记的数量(下标)const markerIndex = fenceAssembly.value.TMapMarker ? fenceAssembly.value.TMapMarker.length : 0;// 生成文本图像(假设这是一个异步操作)generateTextImage(markerIndex + 1, (url) => {const icon = new T.Icon({iconUrl: url,iconAnchor: new T.Point(10, 10)});// 创建标记const marker = new T.Marker(latLng, {icon: icon});fenceAssembly.value.TMapMarker.push(marker); // 直接推入数组,使用数组长度作为索引// 将标记添加到地图上TMap.value.addOverLay(marker);});}// 生成带序号的图片function generateTextImage(text, callback) {// 创建Canvas元素var canvas = document.createElement('canvas');var ctx = canvas.getContext('2d');// 设置Canvas大小canvas.width = 20;canvas.height = 20;ctx.beginPath();ctx.arc(10, 10, 10, 0, 2 * Math.PI);// 绘制背景ctx.fillStyle = primaryColor.value;ctx.fill();// 绘制文字ctx.fillStyle = '#ffffff';ctx.font = '15px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(text || 1, canvas.width / 2, canvas.height / 1.8);// 将Canvas转换为DataURL格式的图片var dataURL = canvas.toDataURL('image/png');callback(dataURL)}// 子组件暴露defineExpose({});</script><template><div id="container" style="height: 100%;width: 100%;"></div>
</template><style lang="less" scoped></style>
3、添加点、删除点、拖拽点
文档
添加:鼠标左键点击地图直接点击添加、鼠标右键弹出二级菜单手动添加,直接在初始化时,通过监听地图 点击/右键菜单 事件,获取到点击点对应的经纬度坐标,而后直接执行添加标注点的方法即可
删除:仅可在鼠标右键标注点的时候通过二级菜单进行,故此直接监听标注点右键事件即可
拖拽:直接监听点的推拽事件 代码示例
右键菜单的实现:直接定义html节点,css设置定位、display为none,监听鼠标右键事件,触发后获取到基于视图窗口的xy坐标,而后display设置为block显示出来即可完成
<script setup>import { onMounted, ref } from 'vue';defineOptions({name: ''});const props = defineProps({})const emits = defineEmits(['on-ok']);onMounted(() => {setTimeout(() => {initTMap();}, 1000);});const TMap = ref(null);const primaryColor = ref('#0249f9');const latlngPointSet = ref('30.065455529211373,120.56547777078745;30.05799538979555,120.56624760591018;30.06226195624971,120.58499353379405'); // 围栏坐标点集const addMarkerPos = ref({}); // 当前要手动添加的morKer的坐标位置const delMarkerIndex = ref(-1); // 当前删除的点下标const fenceAssembly = ref({TMapMarker: [],latlngArr: [],latlngStr: '',polygon: null,})function initTMap() {// 转换坐标const latlngArr = latlngPointSet.value.split(';').map(ele => {const [lat, lng] = ele.split(',');return `${lat},${lng}`});fenceAssembly.value = {TMapMarker: [],polygon: null,latlngArr: latlngArr,latlngStr: latlngArr.join(';'),};// 转换坐标(中心点)const center = new T.LngLat(120.57927905745419, 30.05445160751536);// 初始化地图TMap.value = new T.Map('container');TMap.value.centerAndZoom(center, 15);setupEditMode();operateTMap();}// 编辑模式专用设置function setupEditMode() {// 定义一个辅助函数来检查并处理添加标记点的逻辑const checkAndAddMarker = (event, liJiZX = false) => {if (fenceAssembly.value.latlngArr?.length >= 20) {ElMessage({message: '电子围栏最多允许添加20个坐标点!',type: 'warning',})return;}addMarkerPos.value = event.lnglat;if (liJiZX) {addMarker(true);contextmenuFn(''); // 隐藏右键菜单} else {contextmenuFn('map', event.containerPoint.x, event.containerPoint.y); // 打开右键菜单}};// 监听地图点击事件TMap.value.addEventListener('click', (event) => {checkAndAddMarker(event, true);});// 监听地图右键菜单事件TMap.value.addEventListener('contextmenu', (event) => {checkAndAddMarker(event);});}// 渲染天地图function operateTMap() {if (!TMap.value) return;// 先清除所有覆盖物TMap.value.clearOverLays();// 将顶点渲染为图像标注在地图上const data = fenceAssembly.value.latlngArr || [];// 先移除监听if ((fenceAssembly.value.TMapMarker || []).length > 0) {fenceAssembly.value.TMapMarker.forEach(ele => {ele.removeEventListener('dragend');ele.removeEventListener('contextmenu');})}fenceAssembly.value.TMapMarker = [];data.forEach(ele => {const [lat, lng] = ele.split(',');addMarkerPos.value = new T.LngLat(lng, lat)addMarker();})// 创建并添加多边形对象const points = data.map(ele => {const [lat, lng] = ele.split(',');return new T.LngLat(lng, lat);});if (points.length >= 3) { // 确保有足够的点来创建一个多边形fenceAssembly.value.points = pointsfenceAssembly.value.polygon = new T.Polygon(points, {color: primaryColor.value,weight: 3,opacity: 1,fillColor: "#44aaf8",fillOpacity: 0.3});TMap.value.addOverLay(fenceAssembly.value.polygon);// fenceAssembly.value.polygon.enableEdit();}}// 添加标注点function addMarker(isManualAdd = false) {const latLng = addMarkerPos.value;if (!TMap.value || !latLng) return;// 获取当前标记的数量(下标)const markerIndex = fenceAssembly.value.TMapMarker ? fenceAssembly.value.TMapMarker.length : 0;// 生成文本图像(假设这是一个异步操作)generateTextImage(markerIndex + 1, (url) => {const icon = new T.Icon({iconUrl: url,iconAnchor: new T.Point(10, 10)});// 创建标记const marker = new T.Marker(latLng, {icon: icon});fenceAssembly.value.TMapMarker.push(marker); // 直接推入数组,使用数组长度作为索引// 将标记添加到地图上TMap.value.addOverLay(marker);// 开启拖拽marker.enableDragging();// 监听拖拽完成事件marker.addEventListener('dragend', (event) => {const {lat, lng} = event.lnglat;const latlngStr = `${lat},${lng}`;fenceAssembly.value.latlngArr[markerIndex] = latlngStr; // 使用正确的索引更新数据fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');operateTMap(); // 重新渲染地图以反映更改});// 监听右键菜单事件marker.addEventListener('contextmenu', (event) => {delMarkerIndex.value = markerIndex;contextmenuFn('marKer', event.containerPoint.x, event.containerPoint.y);})// 如果是手动添加,则立即更新围栏数据并调用 operateTMapif (isManualAdd) {const {lat, lng} = latLng;const latlngStr = `${lat},${lng}`;fenceAssembly.value.latlngArr.push(latlngStr); // 如果是手动添加,可能需要推入新元素fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');contextmenuFn('');operateTMap();}});}// 手动删除点function delMarkerFn() {// 删除指定下标的点fenceAssembly.value.latlngArr.splice(delMarkerIndex.value, 1);fenceAssembly.value.latlngStr = fenceAssembly.value.latlngArr.join(';');// 右键菜单隐藏contextmenuFn('');// 重新渲染天地图operateTMap();}// 生成带序号的图片function generateTextImage(text, callback) {// 创建Canvas元素var canvas = document.createElement('canvas');var ctx = canvas.getContext('2d');// 设置Canvas大小canvas.width = 20;canvas.height = 20;ctx.beginPath();ctx.arc(10, 10, 10, 0, 2 * Math.PI);// 绘制背景ctx.fillStyle = primaryColor.value;ctx.fill();// 绘制文字ctx.fillStyle = '#ffffff';ctx.font = '15px Arial';ctx.textAlign = 'center';ctx.textBaseline = 'middle';ctx.fillText(text || 1, canvas.width / 2, canvas.height / 1.8);// 将Canvas转换为DataURL格式的图片var dataURL = canvas.toDataURL('image/png');callback(dataURL)}// 右键菜单操作function contextmenuFn(mode = '', left = null, top = null) {if (mode && left && top) {// 打开指定菜单const contextmenu = document.getElementById('contextmenu_' + mode);contextmenu.style.display = 'block';contextmenu.style.left = left + 'px';contextmenu.style.top = top + 'px';}// 隐层其他右键菜单['map', 'marKer'].filter(item => item !== mode).forEach(item => {const contextmenu = document.getElementById('contextmenu_' + item);contextmenu.style.display = 'none';});}// 子组件暴露defineExpose({});</script><template><div id="mainMap"><div id="container" style="height: 100%;width: 100%;"></div><!-- 地图右键菜单 --><div id="contextmenu_map" class="contextmenu_map"><div class="contextmenu_map_item" @click="addMarker(true)">添加坐标点</div></div><!-- marKer右键菜单 --><div id="contextmenu_marKer" class="contextmenu_marKer"><div class="contextmenu_marKer_item" @click="delMarkerFn">删除</div></div></div>
</template><style lang="less" scoped>
#mainMap {width: 100%;height: 100%;background-color: #f1f1f1;color: #000;position: relative;.tips {position: absolute;right: 10px;z-index: 2000;top: 10px;}.save {position: absolute;left: 10px;z-index: 2000;top: 10px;}.fenceItem {// position: relative;margin-bottom: 10px;.h1Title {border-bottom: none;padding-bottom: 0;margin-bottom: 0;}.butBox {display: flex;}}.mainRight {position: relative;.but {position: absolute;top: 10px;left: 10px;z-index: 400;}}.contextmenu_map,.contextmenu_polygon,.contextmenu_marKer {display: none;position: absolute;z-index: 1000;background-color: #f9f9f9;border: 1px solid #ccc;border-radius: 4px;padding: 5px 0;box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);&_item {padding: 10px 20px;cursor: pointer;}&_item:hover {background-color: #f1f1f1;}}
}
</style>