一、文档
vue3
javascript WGS84、GCj02相互转换
天地图官方文档
注册登录然后申请应用key,通过CDN引入
javascript
<script src="http://api.tianditu.gov.cn/api?v=4.0&tk=您的密钥" type="text/javascript"></script>
二、分析
所谓电子围栏
1、就是在地图商通过经纬度将点标注出来,然后将这些点连起来渲染为一个面
2、然后在此基础上支持编辑即添加删除以及拖拽
3、在每次操作完成之后都对天地图视图重新渲染
三、代码实现
1、在线体验
2、渲染标注点以及面
直接通过坐标点把标注点以及多边形渲染出来
javascript
<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 = points
fenceAssembly.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显示出来即可完成
javascript
<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 = points
fenceAssembly.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);
})
// 如果是手动添加,则立即更新围栏数据并调用 operateTMap
if (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>