效果图
location-info.vue (使用map的vue文件)
<template>
<BaseItem title="地理位置信息">
<Row :gutter="[8, 8]">
<Col :span="12">
<FormItem prop="address" :rules="rule" label="地理位置">
<Input v-model="currentInfo.address" disabled placeholder="请在地图上绘制区域">
<template #iconAfter>
<PositionFill class="icon-after" />
</template>
</Input>
</FormItem>
</Col>
</Row>
<Map @change="handleChange" ref="gridMapRef" @mapComplete="mapComplete" />
</BaseItem>
</template>
<script setup>
import Map from "./map.vue";
......
</script>
map.vue
<template>
<div id="locationMapContainer"></div>
<div class="search-address">
<Search
@blur="locationDetailBlur"
@keydown.enter="locationDetailBlur"
:inputStyle="{ width: '394px' }"
size="large"
placeholder="搜索地图"
v-model="searchAddress"
/>
</div>
<div class="draw-operation">
<div>区域绘制</div>
<div class="draw-icon-box">
<svg-icon
:iconClass="`icon_${key}_${stateSelected[key] ? 'usable' : 'disabled'}`"
:disabled="!stateSelected[key] && areaFlag"
size="32"
v-for="key in GRID_DRAW_AREA_KEYS"
:key="key"
@click="handleDraw(key)"
/>
</div>
<div class="draw-btn-box" v-show="areaFlag">
<Button primary shape="round" @click="handeEditDraw">{{
isEdit ? "完成" : "编辑"
}}</ori-button>
<Button type="outline" shape="round" @click="handleClear">清除</ori-button>
</div>
</div>
</template>
<script setup>
import {
onMounted,
defineProps,
createVNode,
render,
watch,
onBeforeUnmount,
defineExpose,
defineEmits,
nextTick,
ref,
reactive
} from "vue";
import AMapLoader from "@amap/amap-jsapi-loader";
import { useRouter } from "vue-router";
const router = useRouter();
const props = defineProps({
point: {
type: Array
}
});
const BASE_OPTS = {
strokeColor: "#FF33FF",
strokeWeight: 6,
strokeOpacity: 0.2,
fillColor: "#1791fc",
fillOpacity: 0.4,
strokeStyle: "solid"
};
let marker = null;
let map = null;
let geocoder = null;
let instance = null;
let mouseTool = null;
let drawObj = {};
const searchAddress = ref("");
const areaFlag = ref(false);
const stateDraw = reactive({
address: "",
drawPoint: {
type: "",
points: [],
radius: 0
},
centerPoint: null,
area: null
});
const emits = defineEmits(["change", "mapComplete"]);
//区域绘制图形key
const GRID_DRAW_AREA_KEYS = ["square", "circle", "polygon"];
const stateSelected = reactive({
square: false,
circle: false,
polygon: false
});
const isEdit = ref(false);
const initMap = () => {
AMapLoader.load({
key: "高德秘钥",
version: "2.0",
plugins: [
"AMap.Geocoder",
"AMap.MouseTool",
"AMap.PolygonEditor", //多边形编辑器
"AMap.RectangleEditor", //矩形编辑器
"AMap.CircleEditor", //圆编辑器
"AMap.GeometryUtil" //空间数据计算的函数库,计算点线面的距离、长度、面积
]
}).then((AMap) => {
instance = AMap;
map = new AMap.Map("locationMapContainer", {
center: props.point,
zoom: 16
});
// 初始化标记点
if (props.point[0] != 0 && props.point[1] != 0) {
getAddress(props.point);
} else {
map.setCenter([106.553882, 29.561466]);
}
initGeocoder();
initMouseTool();
map.on("complete", function () {
// 地图加载完成后触发
emits("mapComplete");
});
});
};
const initGeocoder = () => {
geocoder = new instance.Geocoder({
// city 指定进行编码查询的城市,支持传入城市名、adcode 和 citycode
// city: "010"
});
};
const initMouseTool = () => {
mouseTool = new instance.MouseTool(map);
mouseTool.on("draw", (event) => {
// event.obj 为绘制出来的覆盖物对象
console.log(event);
areaFlag.value = true;
isEdit.value = false;
drawObj = event;
// 绘制完成后保存多边形实例
geometrical = drawObj.obj;
mouseTool.close();
//获取区域绘制的坐标点、计算中心点、计算面积
getPointsAndCenterAndArea();
});
};
const locationDetailBlur = async () => {
await nextTick();
getLocation(searchAddress.value);
};
// 获取地理坐标信息
const getLocation = (str) => {
if (marker) map.remove(marker);
geocoder?.getLocation(str, (status, { info, geocodes }) => {
if (status === "complete" && info === "OK") {
// result中对应详细地理坐标信息
const { lng, lat } = geocodes[0]?.location;
stateDraw.centerPoint = [lng, lat];
addMark(stateDraw.centerPoint);
}
});
};
//0:初始化/地图标点 1:中心点获取地址
const getAddress = async (point, type = 0) => {
await geocoder?.getAddress(point, (status, { info, regeocode }) => {
if (status === "complete" && info === "OK") {
// result为对应的地理位置详细信息
const { province, city, district } = regeocode.addressComponent;
const detail = regeocode.formattedAddress.replace(province + city + district, "");
const obj = {
formattedAddress: regeocode.formattedAddress,
location: [province, city || province, district],
detail,
point
};
if (type) {
stateDraw.address = obj.formattedAddress;
emits("change", { ...stateDraw });
}
}
});
};
// 添加标记点
const addMark = (point) => {
marker = new instance.Marker({
position: point
});
map.clearMap();
map.add(marker); // 添加到地图
map.setCenter(point);
};
// 绘制矩形
const drawRectangle = (params = {}) => {
mouseTool.rectangle({ ...BASE_OPTS, ...params });
};
//绘制圆
const drawCircle = (params = {}) => {
mouseTool.circle({ ...BASE_OPTS, ...params });
};
//绘制多边形
const drawPolygon = (params = {}) => {
mouseTool.polygon({ ...BASE_OPTS, ...params });
};
/**
* 获取路径点、中心点坐标、计算面积
*/
const getPointsAndCenterAndArea = async () => {
// 如果geometrical不存在,则直接返回
if (!geometrical) return;
// 如果绘制类型为多边形或者矩形,且存在路径点
if (["polygon", "square"].includes(stateDraw.drawPoint.type) && geometrical.getPath()) {
// 获取路径点数组
const points = geometrical.getPath()?.map((point) => [point.lng, point.lat]);
// 将路径点数组赋值给绘图状态的drawPoint属性的points属性
stateDraw.drawPoint.points = points;
// 初始化纬度总和和经度总和
let sumLat = 0;
let sumLng = 0;
// 遍历路径点数组
for (const point of points) {
// 累加纬度总和
sumLat += point[1];
// 累加经度总和
sumLng += point[0];
}
// 计算平均纬度和平均经度
const avgLat = sumLat / points.length;
const avgLng = sumLng / points.length;
// 将平均经纬度坐标赋值给绘图状态的centerPoint属性
stateDraw.centerPoint = [avgLng, avgLat];
// 使用高德地图提供的GeometryUtil工具计算面积,保留两位数
stateDraw.area = instance.GeometryUtil.ringArea(points)?.toFixed(2) || null;
getAddress(stateDraw.centerPoint, 1);
} else if (stateDraw.drawPoint.type === "circle") {
// 圆形变化后,可以直接从geometrical获取圆心坐标
let center = geometrical.getCenter();
stateDraw.centerPoint = [center.lng, center.lat];
stateDraw.drawPoint.radius = geometrical.getRadius();
// 计算圆形面积(单位是平方米),保留两位数
stateDraw.area = (Math.PI * Math.pow(stateDraw.drawPoint.radius, 2))?.toFixed(2) || null;
getAddress(stateDraw.centerPoint, 1);
}
};
const handleClear = () => {
areaFlag.value = false;
stateSelected.square = false;
stateSelected.circle = false;
stateSelected.polygon = false;
mouseTool.close(true);
geometricalEditor && geometricalEditor.close();
resetDraw();
};
const resetDraw = () => {
stateDraw.address = "";
stateDraw.drawPoint.points = [];
stateDraw.drawPoint.type = "";
stateDraw.drawPoint.radius = 0;
stateDraw.centerPoint = null;
stateDraw.area = null;
emits("change", { ...stateDraw });
};
const handleDraw = (key) => {
if (areaFlag.value) return;
mouseTool.close(true);
stateDraw.drawPoint.type = key;
GRID_DRAW_AREA_KEYS.map((p) => (stateSelected[p] = p === key ? true : false));
//绘制矩形
if (key === "square") {
drawRectangle();
} else if (key === "circle") {
//绘制圆
drawCircle();
} else {
//绘制多边形
drawPolygon();
}
};
let geometrical = null;
let geometricalEditor = null;
const handeEditDraw = () => {
isEdit.value = !isEdit.value;
if (isEdit.value && drawObj.type === "draw") {
if (stateSelected.square) {
// 初始化矩阵编辑器
geometricalEditor = new instance.RectangleEditor(map, geometrical);
} else if (stateSelected.circle) {
// 初始化圆编辑器
geometricalEditor = new instance.CircleEditor(map, geometrical);
} else {
// 初始化多边形编辑器
geometricalEditor = new instance.PolygonEditor(map, geometrical);
}
// 开启编辑模式
geometricalEditor.open();
}
if (!isEdit.value && geometricalEditor) {
// 关闭编辑模式
geometricalEditor.close();
//获取区域绘制的坐标点、计算中心点、计算面积
getPointsAndCenterAndArea();
// // 地图缩放到多边形范围
// map.setCenter(point);
}
};
//详情反显区域
const drawDetailGeometrical = (draw, center) => {
let drawPoint = JSON.parse(draw || "{}");
if (!Object.keys(drawPoint)?.length) return;
if (["square", "polygon"].includes(drawPoint.type)) {
//矩形和多边形绘制
const polygon = new instance.Polygon({ path: drawPoint.points, ...BASE_OPTS });
map.add(polygon);
// 缩放地图到合适的视野级别
drawPoint.type === "square" ? map.setFitView([polygon]) : map.setCenter(center);
} else if (drawPoint.type === "circle") {
//圆形绘制
const circle = new instance.Circle({
center,
radius: drawPoint.radius,
...BASE_OPTS
});
map.add(circle);
// 缩放地图到合适的视野级别
map.setFitView([circle]);
}
};
defineExpose({
drawDetailGeometrical
});
watch(
() => props.point,
(newVal) => {
if (map) {
map.setCenter(newVal);
}
}
);
onMounted(() => {
initMap();
});
onBeforeUnmount(() => {
if (map) map.destroy();
});
</script>
<style lang="less" scoped>
#locationMapContainer {
width: 100%;
height: 423px;
margin: 0 12px 16px;
}
.search-address {
position: absolute;
top: 88px;
left: 32px;
:deep {
.input-inner {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 4px 0 rgba(29, 33, 41, 0.28), 0 -1px 0 0 rgba(0, 0, 0, 0.02);
}
.ori-input.large .input-inner,
.ori-input.large .ori-input__icon {
height: 48px;
line-height: 40px;
}
.ori-input.large .input-inner {
padding-left: 52px;
}
.ori-input .ori-icon {
font-size: 24px;
}
.ori-input .ori-input__inner .ori-input__icon.ori-input__icon-before {
left: 16px;
}
}
}
.draw-operation {
position: absolute;
right: 28px;
bottom: 32px;
width: 160px;
padding: 16px 16px 12px;
font-weight: 500;
font-size: 12px;
line-height: 20px;
background: #fcfcfc;
border-radius: 8px;
box-shadow: 0 0 16px -3px rgba(0, 0, 0, 0.5);
.draw-icon-box {
margin-top: 4px;
.svg-icon {
margin-right: 4px;
cursor: pointer;
&:hover {
background: #e8edff;
border-radius: 50%;
}
&[disabled="true"] {
cursor: not-allowed;
&:hover {
background: transparent;
}
}
}
}
.draw-btn-box {
margin-top: 11px;
.ori-button:first-child {
margin-right: 6px;
}
}
}
</style>
iconClass对应的图标:
矩阵选中、禁用图标:
icon_square_usable.svg
<svg t="1709778697403" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1992" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#E8EDFF" p-id="1993"></path><path d="M256 256h512v512H256z" fill="#CBD4FC" p-id="1994"></path></svg>
icon_square_disabled.svg
<svg t="1709778726235" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2139" width="200" height="200"><path d="M512 518.516364m-496.484848 0a496.484848 496.484848 0 1 0 992.969696 0 496.484848 496.484848 0 1 0-992.969696 0Z" fill="#FFFFFF" fill-opacity="0" p-id="2140"></path><path d="M263.757576 270.273939h496.484848v496.484849H263.757576z" fill="#F2F3F5" p-id="2141"></path></svg>
圆选中、禁用图标:
icon_circle_usable.svg
<svg t="1709778742996" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2284" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#E8EDFF" p-id="2285"></path><path d="M224 224m288 0l0 0q288 0 288 288l0 0q0 288-288 288l0 0q-288 0-288-288l0 0q0-288 288-288Z" fill="#CBD4FC" p-id="2286"></path></svg>
icon_circle_disabled.svg
<svg t="1709778755766" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2433" width="200" height="200"><path d="M232.727273 239.243636m279.272727 0l0 0q279.272727 0 279.272727 279.272728l0 0q0 279.272727-279.272727 279.272727l0 0q-279.272727 0-279.272727-279.272727l0 0q0-279.272727 279.272727-279.272728Z" fill="#F2F3F5" p-id="2434"></path></svg>
多边形选中、禁用图标:
icon_polygon_usable.svg
<svg t="1709778777761" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2579" width="200" height="200"><path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#E8EDFF" p-id="2580"></path><path d="M670.016 241.664L826.08 512l-157.024 272H405.44L244.512 314.752l425.504-73.088z" fill="#CBD4FC" p-id="2581"></path></svg>
icon_polygon_disabled.svg
<svg t="1709778790882" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2726" width="200" height="200"><path d="M512 518.516364m-496.484848 0a496.484848 496.484848 0 1 0 992.969696 0 496.484848 496.484848 0 1 0-992.969696 0Z" fill="#FFFFFF" fill-opacity="0" p-id="2727"></path><path d="M665.227636 256.372364L816.562424 518.516364l-152.265697 263.757575H408.669091L252.617697 327.245576l412.609939-70.873212z" fill="#F2F3F5" p-id="2728"></path></svg>