背景
本次分享的实战案例项目名为"智慧街道一体化平台"。由于开发时间紧迫,我采用了Vue2框架进行前后端架构开发,并基于高德地图的JS API和Web基础服务API,实现了一个简单的街道管理项目。客户需要可视化大屏展示,因此我们重点展示了街道社区在地图中的效果。
目标
客户关于街道可视化部分的主要需求包括:在地图上展示街道全貌,实现街道中社区的区域化展示效果;通过序号标注小区,并在点击标注点位时将地图视角定位并放大到对应社区;同时,在信息窗体中显示对应小区的相关信息。
方案设计
1、街道的边界区域化
为了实现对街道边界的区域化展示,我查阅了JS API文档,并采用了多边形AMap.Polygon功能。通过该功能,我们可以方便地绘制出街道的边界。
官方功能示意图
demo效果图
完整示例代码如下。
js
const streetCoordinates = [
[
[119.017987, 33.616876],
[119.019426, 33.616761],
此处省略我当时手动采集的街道边界的经纬度坐标点.....
],
];
var HuaihaiPolyline = new AMap.Polyline({
path: streetCoordinates,
isOutline: true,
outlineColor: "#D9251C",
borderWeight: 3,
strokeColor: "#D9251C",
strokeOpacity: 1,
strokeWeight: 6,
// 折线样式还支持 'dashed'
strokeStyle: "dashed",
// strokeStyle是dashed时有效
strokeDasharray: [15, 5],
lineJoin: "round",
lineCap: "round",
zIndex: 50,
strokeWeight: 1,
});
this.map.add([HuaihaiPolyline]);
2、社区范围的区域化显示
在该街道中共有7个社区。为了方便展示,我手动采集了社区的坐标点位,并通过不同色块展示了街道中的社区。
覆盖物效果图
覆盖物功能采集的demo完整代码如下。
js
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<style>
html,
body,
container {
width: 100%;
height: 100%;
}
</style>
<title>多边形编辑器吸附功能</title>
<link rel="stylesheet" href="https://a.amap.com/jsapi_demos/static/demo-center/css/demo-center.css" />
<script
src="https://webapi.amap.com/maps?v=2.0&key=09023f93f5e47cb8087795cff9eadec5&plugin=AMap.PolygonEditor">
</script>
<script src="https://a.amap.com/jsapi_demos/static/demo-center/js/demoutils.js"></script>
</head>
<body>
<div id="container"></div>
<div class="input-card" style="width: 120px">
<button class="btn" onclick="createPolygon()" style="margin-bottom: 5px">新建</button>
<button class="btn" onclick="polyEditor.open()" style="margin-bottom: 5px">开始编辑</button>
<button class="btn" onclick="polyEditor.close()">结束编辑</button>
<button class="btn" onclick="test()">获取经纬度数组</button>
</div>
<script type="text/javascript">
var map = new AMap.Map("container", {
center: [116.471354, 39.994257],
zoom: 16.8
});
// 1号小区
var path1 = [
[
119.017987,
33.616882
],
[
119.01945,
33.616795
],
[
119.023023,
33.615225
],
[
119.027848,
33.614045
],
[
119.027083,
33.612268
],
[
119.024476,
33.613368
],
[
119.024079,
33.613259
],
[
119.023838,
33.613042
],
[
119.023734,
33.61289
],
[
119.023573,
33.612815
],
[
119.023399,
33.61258
],
[
119.019942,
33.613188
],
[
119.019465,
33.613172
],
[
119.01884,
33.615088
]
]
// 2号小区
var path3 = [
[
119.030715,
33.603081
],
[
119.029033,
33.599871
],
[
119.028916,
33.599187
],
[
119.030978,
33.599661
],
[
119.031845,
33.599977
],
[
119.032563,
33.600343
],
[
119.033177,
33.601044
],
[
119.031667,
33.602829
],
[
119.031161,
33.602987
]
]
// 3号
var path4 = [
[
119.032683,
33.599499
],
[
119.032617,
33.599665
],
[
119.032531,
33.599818
],
[
119.032382,
33.600133
],
[
119.032594,
33.60027
],
[
119.032743,
33.600443
],
[
119.033037,
33.600258
],
[
119.033209,
33.599652
],
[
119.033302,
33.599331
],
[
119.033332,
33.599153
],
[
119.033322,
33.598985
],
[
119.032735,
33.598823
],
[
119.032358,
33.598742
],
[
119.03196,
33.598664
],
[
119.031566,
33.599015
],
[
119.032166,
33.599237
],
[
119.03256,
33.599382
],
[
119.032648,
33.599447
]
]
// 4
var path5 = [
[
119.028981,
33.599134
],
[
119.03123,
33.599657
],
[
119.03135,
33.599181
],
[
119.031609,
33.598887
],
[
119.029051,
33.598446
]
]
var path2 = [
[119.030785, 33.604298],
[119.032148, 33.604396],
]
var polygon1 = new AMap.Polygon({
path: path1
})
var polygon2 = new AMap.Polygon({
path: path2
})
var polygon3 = new AMap.Polygon({
path: path3
})
var polygon4 = new AMap.Polygon({
path: path4
})
var polygon5 = new AMap.Polygon({
path: path5
})
map.add([polygon1, polygon2, polygon3, polygon4, polygon5]);
map.setFitView();
var polyEditor = new AMap.PolygonEditor(map);
polyEditor.addAdsorbPolygons([polygon1, polygon2]);
polyEditor.on('add', function (data) {
console.log(data);
var polygon = data.target;
polyEditor.addAdsorbPolygons(polygon);
polygon.on('dblclick', () => {
polyEditor.setTarget(polygon);
polyEditor.open();
})
})
polygon1.on('dblclick', () => {
polyEditor.setTarget(polygon1);
polyEditor.open();
})
polygon2.on('dblclick', () => {
polyEditor.setTarget(polygon2);
polyEditor.open();
})
function createPolygon() {
polyEditor.close();
polyEditor.setTarget();
polyEditor.open();
}
polyEditor.setTarget(polygon2);
polyEditor.open();
function test(params) {
console.log(polygon2.getPath()); // 输出多边形的轮廓线节点数组
console.log(polygon2.getOptions()); // 返回覆盖物的中心点的经纬度坐标
}
</script>
</body>
通过该demo网页手动画出覆盖物区域,最后点击获取经纬度数组按钮即可在浏览器控制台打印出来。
获取覆盖物经纬度demo效果图
3、小区marker点采集
对于小区marker点的采集,我采用了管理员在平台添加小区时一并添加小区坐标点的方式。这样,无论后续新增多少小区,都可以由街道管理员自行添加或修改坐标点位。
具体解决方案是,管理员在新增小区的时候,先输入小区名称,通过点击查询小区坐标点,这里调用高德开放平台的POI高级搜索API功能,获取到小区的坐标点,最后点击创建即可。这里要非常感谢高德开放平台的web服务有每日免费额度,为我们开发者提供了便利。
通过POI服务获取小区坐标点demo的完整代码如下。
js
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
<title>输入提示后查询POI</title>
<link rel="stylesheet" href="https://cache.amap.com/lbs/static/main1119.css" />
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<style type="text/css">
panel {
position: absolute;
background-color: white;
max-height: 90%;
overflow-y: auto;
top: 10px;
right: 10px;
width: 280px;
}
</style>
<script type="text/javascript" src="https://webapi.amap.com/maps?v=2.0&key=09023f93f5e47cb8087795cff9eadec5">
</script>
<script src="../js/vue@v2.6.14.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="../js/axios@1.3.4.js"></script>
</head>
<body>
<div id="app">
<div id="container"></div>
<div id="panel"></div>
<div id="myPageTop">
<table>
<tr>
<td>
<label>请输入关键字:</label>
</td>
</tr>
<tr>
<td>
<input v-model="address" id="tipinput" />
</td>
</tr>
<tr>
<td>
<label>请输入城市:</label>
</td>
</tr>
<tr>
<td>
<input v-model="city" id="tipinput" />
</td>
</tr>
<tr>
<td>
<button @click="btn()">开始查询</button>
</td>
</tr>
</table>
</div>
</div>
<script type="text/javascript">
new Vue({
el: '#app',
data: {
msg: 'Hello Vue!',
address: "向阳人家",
city: "淮安市",
addressList: [], //住宅列表
map: null, //地图实例
firstLocation: null, // 存储首个maker的位置
},
mounted() {
console.log(this.msg);
this.init()
},
methods: {
addMarkersToMap() {
this.firstLocation = this.addressList[0].location.split(',').map(parseFloat);
this.addressList.forEach((location, index) => {
const position = location.location.split(',').map(parseFloat);
const marker = new AMap.Marker({
position: new AMap.LngLat(position[0], position[1]),
// content: `<div>${location.name}</div>` // 如果需要显示名称的话
});
marker.setMap(this.map);
// 如果是第一个maker,添加地图平移和缩放
if (index === 0) {
this.map.setCenter(new AMap.LngLat(this.firstLocation[0], this
.firstLocation[1])); // 移动地图中心到第一个maker位置
this.map.setZoom(15); // 设置地图缩放到合适的级别,您可以根据实际情况调整
}
}
marker.on('click', () => {
const currentPos = marker.getPosition(); // 获取当前位置
console.log(`点击的maker坐标:${currentPos.lng}, ${currentPos.lat}`);
});
});
},
// 已有btn方法...
init() {
var that = this
this.map = new AMap.Map("container", {
resizeEnable: true
});
},
getinfo() {
axios.get(
'https://restapi.amap.com/v3/place/text?keywords=淮安&city=beijing&offset=20&page=1&key=67164699cd494100c2e6ec9e75257a2c&extensions=all'
).then(res => {
console.log("请求结果", res);
})
},
btn() {
axios.get(
`https://restapi.amap.com/v3/assistant/inputtips?output=JSON&city=${this.city}&keywords=${this.address}&citylimit=true&key=67164699cd494100c2e6ec9e75257a2c`
).then(res => {
console.log("请求结果", res);
console.log("请求结果22", res.data.tips);
if (res.data.tips.length > 0) {
this.addressList = res.data.tips
this.addMarkersToMap()
}
})
}
},
})
</script>
</body>
效果展示
通过接口获取marker点列表后,我为每个marker添加了点击事件。点击后,地图会定位并放大到对应位置。最终实现的网页效果如下所示。
大屏效果图
最终我将网页中地图区域的内容完整的封装成了一个Vue组件,完整代码如下。
js
<template>
<div id="container"></div>
</template>
<script>
import AMapLoader from "@amap/amap-jsapi-loader";
// import { COORDINATES } from "@/utils/position.min"; // 导入住宅覆盖物坐标数组
import { COORDINATES } from "@/utils/positionArea"; // 导入住宅覆盖物坐标数组
import { getList as getResidenceList } from "@/api/streetManage/residenceManage";
export default {
name: "map-view",
props: {
// 当前住宅
currentLsitData: {
type: Object,
},
},
watch: {
// 监听当前点击的住宅列表的变化
currentLsitData: {
handler(newVal) {
this.setMapViewMiddle(newVal);
},
},
},
data() {
return {
infoWindow: null, //信息窗体
map: null, //地图实例
numberList: [], //住宅列表
};
},
async mounted() {
await this.getResidenceList();
await this.initAMap();
},
unmounted() {
this.map?.destroy();
},
methods: {
// 查询住宅列表
async getResidenceList() {
let queryParams = {
pageNum: 1,
pageSize: 100,
};
let res = await getResidenceList(queryParams);
const newData = res.rows.map((item) => ({
...item,
location: item.location.split(",").map(parseFloat),
}));
this.numberList = newData;
},
// 设置地图当前中心点位
setMapViewMiddle(newVal) {
this.map.setCenter(newVal.location);
this.map.setZoom(18);
},
// 初始化地图
initAMap() {
AMapLoader.load({
key: "09023f93f5e47cb8087795cff9eadec5", // 申请好的Web端开发者Key,首次调用 load 时必填
version: "2.0", // 指定要加载的 JSAPI 的版本,缺省时默认为 1.4.15
plugins: ["AMap.Scale"], //需要使用的的插件列表,如比例尺'AMap.Scale',支持添加多个如:['...','...']
})
.then((AMap) => {
var that = this;
this.map = new AMap.Map("container", {
// 设置地图容器id
viewMode: "3D", // 是否为3D地图模式
// zoom: 13, // 初始化地图级别
center: [119.032116, 33.603177], // 初始化地图中心点位置
resizeEnable: true,
mapStyle: "amap://styles/dark", //设置地图的显示样式
pitch: 10, //地图俯仰角度,有效范围 0 度- 83 度
rotateEnable: true, //是否开启地图旋转交互 鼠标右键 + 鼠标画圈移动 或 键盘Ctrl + 鼠标左键画圈移动
pitchEnable: true, //是否开启地图倾斜交互 鼠标右键 + 鼠标上下移动或键盘Ctrl + 鼠标左键上下移动
zoom: 29, //初始化地图层级
rotation: -15, //初始地图顺时针旋转的角度
terrain: true, //开启地形图
// features: ["bg", "road", "point","building"],
// mapStyle: "amap://styles/light",
// layers: [
// // 高德默认标准图层
// new AMap.TileLayer.Satellite(),
// // 楼块图层
// new AMap.Buildings({
// zooms: [16, 18],
// zIndex: 10,
// heightFactor: 2, //2倍于默认高度,3D下有效
// }),
// ],
});
const points = this.numberList;
const streetCoordinates = [
[
[119.017987, 33.616876],
[119.019426, 33.616761],
[119.023956, 33.614935],
[119.027885, 33.614054],
[119.029664, 33.613541],
[119.031662, 33.612804],
[119.033995, 33.611714],
[119.035281, 33.611063],
[119.036521, 33.610694],
[119.037669, 33.609992],
[119.03871, 33.6091],
[119.039827, 33.607965],
[119.041144, 33.60646],
[119.043777, 33.605198],
[119.043239, 33.602682],
[119.044324, 33.600741],
[119.045281, 33.598641],
[119.043207, 33.59912],
[119.038866, 33.598773],
[119.034334, 33.59803],
[119.02527, 33.596435],
[119.021408, 33.606749],
[119.019844, 33.611825],
[119.019142, 33.614297],
[119.017986, 33.616876],
],
];
const createTextStyle = (styleName) => {
switch (styleName) {
case "1":
return {
width: "0.3rem",
height: "0.3rem",
borderRadius: "50%",
backgroundColor: "#0093DD",
color: "#fff",
textAlign: "center",
fontSize: "0.2rem",
lineHeight: "0.3rem",
fontWeight: "bold",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
case "2":
return {
width: "0.3rem",
height: "0.3rem",
borderRadius: "50%",
backgroundColor: "#00923F",
color: "#fff",
textAlign: "center",
fontSize: "0.2rem",
lineHeight: "0.3rem",
fontWeight: "bold",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
case "3":
return {
width: "0.3rem",
height: "0.3rem",
borderRadius: "50%",
backgroundColor: "#E67817",
color: "#fff",
textAlign: "center",
fontSize: "0.2rem",
lineHeight: "0.3rem",
fontWeight: "bold",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
default:
return {
width: "50px",
height: "50px",
borderRadius: "50%",
backgroundColor: "skyblue",
color: "#fff",
textAlign: "center",
fontSize: "0.3rem",
lineHeight: "50px",
fontWeight: "bold",
display: "flex",
justifyContent: "center",
alignItems: "center",
};
}
};
var HuaihaiPolyline = new AMap.Polyline({
path: streetCoordinates,
isOutline: true,
outlineColor: "#D9251C",
borderWeight: 3,
strokeColor: "#D9251C",
strokeOpacity: 1,
strokeWeight: 6,
// 折线样式还支持 'dashed'
strokeStyle: "dashed",
// strokeStyle是dashed时有效
strokeDasharray: [15, 5],
lineJoin: "round",
lineCap: "round",
zIndex: 50,
strokeWeight: 1,
});
this.map.add([HuaihaiPolyline]);
this.map.setFitView();
for (var i = 0; i < COORDINATES.length; i++) {
var area = COORDINATES[i];
// 2.动态添加多个添加覆盖物
var polygon = new AMap.Polygon({
path: area.path,
strokeWeight: 6,
strokeOpacity: 0.2,
fillOpacity: 0.4,
zIndex: 50,
bubble: false,
strokeStyle: "dashed",
});
if (area.areaId == 1) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#B3A6A1",
});
} else if (area.areaId == 2) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#A6B5C7",
});
} else if (area.areaId == 3) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#7A8BC3",
});
} else if (area.areaId == 4) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#BFA479",
});
} else if (area.areaId == 5) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#95A675",
});
} else if (area.areaId == 6) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#B9B36A",
});
} else if (area.areaId == 7) {
polygon.setOptions({
strokeColor: "#896A6A",
fillColor: "#A7A2B1",
});
}
this.map.add(polygon);
}
this.infoWindow = new AMap.InfoWindow({
isCustom: true,
offset: new AMap.Pixel(16, -45),
});
console.log("@points", points);
for (var i = 0; i < points.length; i++) {
var point = points[i];
// 1.动态添加多个make点
var text = new AMap.Text({
text: i + 1,
anchor: "center", // 设置文本标记锚点
draggable: false,
cursor: "pointer",
angle: 10,
style: createTextStyle(point.type),
position: point.location,
});
text.on("click", function (e) {
console.log("被点击", e);
that.onMarkerClick(e);
});
// 2.动态添加多个添加覆盖物
// var polygon = new AMap.Polygon({
// path: point.path,
// strokeWeight: 6,
// strokeOpacity: 0.2,
// fillOpacity: 0.4,
// zIndex: 50,
// bubble: false,
// strokeStyle: "dashed",
// });
// if (point.type === "1") {
// polygon.setOptions({
// strokeColor: "#0094D9",
// fillColor: "#76C1E1",
// });
// } else if (point.type === "2") {
// polygon.setOptions({
// strokeColor: "#01913C",
// fillColor: "#7DC699",
// });
// } else if (point.type === "3") {
// polygon.setOptions({
// strokeColor: "#E57915",
// fillColor: "#EBB17F",
// });
// }
this.map.add(text);
// this.map.add(polygon);
}
})
.catch((e) => {
console.log(e);
});
},
// 地图maker点点击事件
onMarkerClick(e) {
const targetPosition = e.target._position;
let clickedItem = null;
this.numberList.forEach((item) => {
if (
item.location[0] === targetPosition[0] &&
item.location[1] === targetPosition[1]
) {
clickedItem = item;
return;
}
});
if (clickedItem) {
console.log("clickedItem", clickedItem);
const typeName = this.getResidentialTypeName(clickedItem.type);
const customInfo = `
<div class='custom-info-window'>
<!-- 标题区域 -->
<div class='header-section'>
<img src="https://webapi.amap.com/images/autonavi.png" alt="高德标志" style="float:left;width:67px;height:16px;">
<!-- 新增关闭按钮 -->
<span class='close-button'>关闭</span>
</div>
<!-- 主体内容区域 -->
<div class='body-section'>
<p class='detail-item'>小区名称 : ${clickedItem.name}</p>
<p class='detail-item'>小区类型 : ${typeName}</p>
<!-- 其他静态或动态信息 -->
<!-- <p class='detail-item'>电话 : 010-84107000</p> -->
<!-- <p class='detail-item'>邮编 : 223001</p> -->
<p class='other-info'>其他信息:暂无</p>
</div>
</div>
<!-- 背景色 -->
<style>
.custom-info-window {
background-color: white;
padding: 15px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-size: 14px;
line-height: 1.5;
color: #333;
overflow: hidden;
}
.header-section {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.info-title h4, .community-type {
margin: 0;
}
.detail-item, .other-info {
margin-bottom: 5px;
}
.close-button {
float: right;
margin-left: 10px;
cursor: pointer;
color:#1296db;
font-size: 0.225rem;
}
</style>
`;
const bindCloseEvent = () => {
const closeButton = document.querySelector(
".custom-info-window .close-button"
);
closeButton.addEventListener("click", this.closeInfoWindow);
};
this.infoWindow.setContent(customInfo);
this.infoWindow.open(this.map, e.target.getPosition());
this.$emit("getChartInfo");
bindCloseEvent();
} else {
console.log("No item found for the given position");
}
},
// 住宅类型判断
getResidentialTypeName(type) {
switch (type) {
case 1:
return "一类住宅";
case 2:
return "二类住宅";
case 3:
return "三类住宅";
default:
return "未知类型";
}
},
// 关闭信息窗体
closeInfoWindow() {
this.infoWindow.close();
},
},
};
</script>
<style scoped lang="scss">
#container {
width: 100%;
height: 100%;
}
</style>
高德开放平台第二期实战案例,三等奖作品
作者:谢洋
仅代表作者个人观点