【Vue】MineData 地图接入
一、文档资料:
MineData开放平台:
https://minedata.cn/md-platform/login/login
MineData V2.1.0 接口文档:
http://113.108.157.29:7070/support/static/api/doc/js/v2.1.0/api-reference/index.html#map
MineData 在线实例:
http://113.108.157.29:7070/support/api/demo/js-api/zh/map/base/map-show
二、上手案例:
- 1、引入方式:
1、可以直接在打包的index.html页面写script脚本接入
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="renderer" content="webkit">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>projectName</title>
<script type="text/javascript" src="static/config.js"></script>
<!-- 引入MineMap API插件 -->
<link rel="stylesheet" href="http://minedata.cn/minemapapi/v2.1.1/minemap.css">
<script src="http://minedata.cn/minemapapi/v2.1.1/minemap.js"></script>
</head>
<body style="background: #f5f7f8">
<div id="app"></div>
</body>
</html>
2、或者在Vue编写动态接入的方法 import-js.js
import axios from 'axios' const loadJs = src => {
return new Promise((resolve, reject) => {
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = src
document.body.appendChild(script) script.onload = () => {
resolve(script)
}
script.onerror = err => {
console.log(`loadJsFail: ${err}`)
reject()
}
})
} const loadCss = src => {
return new Promise((resolve, reject) => {
axios.get(src).then(res => {
const css = document.createElement('style')
css.type = 'text/css'
css.innerHTML = res.data
document.head.appendChild(css)
resolve(css)
}).catch(err => {
console.log(`loadCssFail: ${err}`)
reject()
})
})
} export {
loadJs,
loadCss
}
在具体某个需要使用的组件中初始化资源:
import { loadCss, loadJs } from './存放路径/import-js.js' async created() {
await loadCss('http://minedata.cn/minemapapi/v2.1.1/minemap.css')
await loadJs('http://minedata.cn/minemapapi/v2.1.1/minemap.js')
}
- 2、地图初始化:
需要先赋值默认的参数变量,这里先用配置文件放好加载的变量 config.js
window._config = {
MAP_LAT: 29.670908,
MAP_LNG: 115.963214, minemapKey: '应用key,开放平台账号创建',
minemapWhiteSolution: '地图样式id',
minemapBlackSolution: '地图样式id',
minemapDomainUrl: 'https://minemap.minedata.cn',
minemapDataDomainUrl: 'https://minemap.minedata.cn',
minemapServerDomainUrl: 'https://sd-data.minedata.cn',
minemapSpriteUrl: 'https://minemap.minedata.cn/minemapapi/v2.1.1/sprite/sprite',
minemapServiceUrl: 'https://service.minedata.cn/service'
}
地图初始化还是基于dom对象创建的,所以所有关于地图加载的方法一定要放到mounted周期下执行
mounted() {
this.initialMapContainer()
},
methods: {
async initialMapContainer() {
window.minemap.domainUrl = window._config.minemapDomainUrl
window.minemap.dataDomainUrl = window._config.minemapDataDomainUrl
window.minemap.serverDomainUrl = window._config.minemapServerDomainUrl
window.minemap.spriteUrl = window._config.minemapSpriteUrl
window.minemap.serviceUrl = window._config.minemapServiceUrl
window.minemap.key = window._config.minemapKey
window.minemap.solution = window._config.minemapBlackSolution
this.mapInstance = new window.minemap.Map({
container: 'mapContainer',
style: `https://service.minedata.cn/map/solu/style/${window.minemap.solution}`,
center: [window._config.MAP_LNG, window._config.MAP_LAT],
zoom: 12, // 缩放大小直
pitch: 0, // 倾斜度参数
projection: window.minemap.ProjectionType.LATLON // 经纬度投影(根据实际项目情况使用,线上环境点位偏移,坐标参数不是配套的)
})
}
}
- 3、打点位:
addStationMarker(station) {
// 创建点位的元素
const isOnline = station['runStatus'] === 1
const markerDom = window.document.createElement('div')
markerDom.id = `station${station.id}`
markerDom.style['background-image'] = `url(${isOnline ? this.stationIconPath2 : this.stationIconPath})`
markerDom.style.width = '50px'
markerDom.style.height = '50px'
markerDom.style['border-radius'] = '2px' // 入参dom元素, 和元素的偏离参数(基于图标像素调整)
const mineMarker = new window.minemap.Marker(markerDom, { offset: [-25, -50] })
// 点位经纬度参数对象
const markerLngLat = new window.minemap.LngLat(station.longitude, station.latitude)
mineMarker.setLngLat(markerLngLat)
// addTo方法将点位展示在地图上
mineMarker.addTo(this.mapInstance)
}
图片资源我使用require直接转为具体对象来渲染的
stationIconPath: require('@/assets/image/station-point-1.png'),
stationIconPath2: require('@/assets/image/station-point-2.png'),
如果在非打包的public目录下,可以直接写路径访问
- 4、创建点位信息窗体:
在点位创建的基础上追加弹窗窗体
addStationPopup(station, mineMarker) {
const isOnline = station['runStatus'] === 1
const markerLngLat = new window.minemap.LngLat(station.longitude, station.latitude)
// 参数说明:
// (1)closeButton,true表示会展示一个关闭按钮;
// (2)closeOnClick,设置为true表示当地图被点击时该信息窗体会被关闭;
// (3)offset,参数为点位置相对于其左上角偏移像素大小;
// (4)anchor,停靠点,值域为[top,bottom,left,right,top-left,top-right,bottom-left,bottom-right],如果不设置该参数,则会根据map container动态调整。
// (5)autoPan,设置为true时,当地图拖动到看不到popup的时候,自动将地图平移到可以看到popup,此参数只对固定popup有效;
const stationPopup = new window.minemap.Popup({
closeOnClick: false,
closeButton: true,
anchor: 'bottom',
offset: [0, -40],
autoPan: false
})
stationPopup.setLngLat(markerLngLat) // 弹窗内容创建
const popupContent = window.document.createElement('div')
popupContent.className = isOnline ? 'station-popup online' : 'station-popup offline'
popupContent.innerHTML = `
<h3>${station.stationName} ${station['stationLocation']}</h3>
<p>区域编码:${station.regionCode}</p>
<p>站点编号:${station.stationNo}</p>
<p>站点状态:${station['runStatus'] === 1 ? '在线' : '离线'}</p>
`
stationPopup.setDOMContent(popupContent)
// stationPopup.addTo(this.mapInstance) /* 默认不打开弹窗 */
mineMarker.setPopup(stationPopup)
}
预览内容:
窗体的样式可以直接修改:
/* mineMap地图窗体样式设置 */
/deep/ .minemap-popup-content {
background: rgba(26,32,62,.7) !important;
min-height: 100px;
padding-top: 10px;
overflow-y: auto;
min-width: 200px;
max-width: 500px !important;
color: #D0D3DA;
} /* 关闭按钮调整 */
/deep/ .minemap-popup-close-button {
font-size: 20px;
color: #D0D3DA;
right: 8px;
top: 6px;
}
/* 窗体下标箭头的颜色调整 */
/deep/ .minemap-popup-tip {
border-top-color: rgba(26,32,62,.7) !important;
}
/* 窗体内部容器元素样式设置 */
/deep/ .station-popup {
padding: 5px;
width: 250px;
font-size: 14px;
}
/deep/ .station-popup > p {
margin-top: 10px;
}
- 3.1 移动居中的点位:
地图平移API,有两种,相对平移和绝对平移:
http://113.108.157.29:7070/support/api/demo/js-api/zh/map/state/map-move
panBy基于相对位置平移,panTo基于点位绝对平移
function moveMap(vPixel, hPixel) {
if (map) {
map.panBy([vPixel, hPixel]);
}
} function moveMapCenter() {
if (map) {
map.panTo([116.46, 39.92]);
}
}
- 4.1 特殊窗体信息样式开发:
真是想不通为啥一定要这个样式,写起来真麻烦
可以发现这里是需要两种窗体样式的,一个是入口样式,一个是出口展示
所以不能直接对mineData的样式设置的,这样就不支持两种或多个窗体样式了
解决思路也是参考原型系统来的,我发现原型系统的默认弹窗是隐藏不展示的
这样就按我们自己声明的类名实现动态效果
第一步先把mineData的信息弹窗隐藏
/* 弹窗信息弹层不支持动态样式,隐藏展示 */
/deep/ .minemap-popup-content {
max-width: none; /* 移除最大宽度 */
overflow: visible !important; /* 溢出的时直接展示,不要滚动条 */
background: none; /* 元素无背景 */
box-shadow: none; /* 元素无阴影 */
padding: 0; /* 内边距0 */
}
然后再是弹窗边框,内部布局这些内容:
/* 驶入弹窗边框 */
/deep/ .drive-in{
border: 1px solid #62b500;
box-shadow: 0 0 10px 0 #62b50045;
}
/* 驶入小箭头 */
/deep/ .station-popup.drive-in:after {
border-color: #62b500 transparent transparent transparent;
}
/* 驶出弹窗边框 */
/deep/ .drive-out{
border: 1px solid #f54336;
box-shadow: 0 0 10px 0 #f5433645;
}
/* 驶出小箭头 */
/deep/ .station-popup.drive-out:after {
border-color: #f54336 transparent transparent transparent;
}
/* 移除默认的下标箭头 */
/deep/ .minemap-popup-tip {
display: none;
}
/* 入 */
/deep/ .station-popup.drive-in > div:first-child {
background: #62b500;
color: white;
width: 40px;
float: left;
line-height: 120px;
text-align: center;
font-size: 16px;
}
/* 出 */
/deep/ .station-popup.drive-out > div:first-child {
background: #f54336;
width: 40px;
color: white;
line-height: 120px;
text-align: center;
font-size: 16px;
}
/deep/ .sfz_name {
font-weight: 800;
}
/deep/ .station-pop-info {
padding: 5px;
}
/* 关闭按钮调整 */
/deep/ .minemap-popup-close-button {
font-size: 18px;
right: 0;
top: 5px;
}
弹窗初始化方法:
addStationPopup(station, mineMarker) {
const isDriveIn = station.type === '1'
const markerLngLat = new window.minemap.LngLat(station.longitude, station.latitude)
const stationPopup = new window.minemap.Popup({
closeOnClick: false,
closeButton: true,
anchor: 'bottom',
offset: [0, isDriveIn ? -48 : -51],
autoPan: false /* 关闭自动拖动 */
})
stationPopup.setLngLat(markerLngLat)
const popupContent = window.document.createElement('div')
popupContent.className = isDriveIn ? 'station-popup drive-in' : 'station-popup drive-out'
popupContent.innerHTML = `
<div>${isDriveIn ? '入' : '出'}</div>
<div class="station-pop-info">
<p class="sfz_name">${isDriveIn ? this.record.cashName : this.record.outCashName}</p>
<p><span>所在区划:</span>${isDriveIn ? this.record.cashArea : this.record.outCashArea}</p>
<p><span>所在高速:</span>${isDriveIn ? this.record.highwayName : this.record.outHighwayName}</p>
<p>时间:${isDriveIn ? this.record.driveTime : this.record.outDriveTime}</p>
</div>
`
stationPopup.setDOMContent(popupContent)
stationPopup.addTo(this.mapInstance)
mineMarker.setPopup(stationPopup)
}
2023年11月02日更新:
- 5 迁徙图 / 飞行图
新功能有一个飞行图的要求,同事是说在Echarts里面有,叫迁徙图,不过我没在文档实例上看到
我想了下这个东西在mineData应该提供了支持,找了下确实有的
http://113.108.157.29:7070/support/api/demo/js-api/zh/layer/plugin/od-1
归类在这图层插件里面:
还有一个是动态航线的图层,效果类似
http://113.108.157.29:7070/support/api/demo/js-api/zh/layer/special/airline-layer
注意要先引入echarts的插件js,不然找不到这个对象
<script src="http://minedata.cn/minemapapi/v2.1.0/plugins/echarts/echarts.3.8.5.min.js"></script>
<script src="http://minedata.cn/minemapapi/v2.1.0/plugins/template/template.js"></script>
2023年11月28日更新:
功能进入测试阶段发现了一个问题,因为迁徙图的轨迹记录要随着主查询的数量不同而更新地图的展示
结果忘了写更新操作,这里又追补查看了下文档
要每次渲染图层之前,先删除之前渲染的图层
一、先说线图层的:
/**
* 创建地图对象后,开始加载地图资源,地图资源加载完成后触发load事件
*/
map.on('load', function () {
// 增加自定义数据源、自定义图层
addSources();
addLayers();
});
线图层是通过追加Source和Layer实现的,所以肯定有对应的删除方法
按ID获取Layer,如果存在时再删除即可,不要直接删除,有可能Layer对象没有初始化
同理Sources也是一样的
迁徙图是在地图中嵌入了Echarts实现的,这个方法已经写好了尚未测试结果...
主要是在文档里面也没找到,待后续更新
2023年11月21日更新:
- 6 点位选取功能
需求是这样,主查询有个采集点位下拉选择,检索方式有两种
在下拉列表中选取
在点位图上选取
查询条件项设置:
<el-form-item label="采集点位">
<el-select v-model="queryForm.stationNo" placeholder="请选择" clearable>
<el-option v-for="(item, idx) in stationList" :key="`station${idx}`" :label="`${item.stationNo} | ${item.stationName}`" :value="`${item.stationNo}`" />
</el-select>
<el-button round type="default" size="small" icon="el-icon-map-location" circle @click="openStationChoose" />
</el-form-item>
点位选取弹窗和组件:
<el-dialog
title="站点选取"
:append-to-body="true"
:close-on-click-modal="false"
:visible="stationChooseVisible"
width="75vw"
custom-class="fixed-dialog"
@close="stationChooseVisible = false"
>
<station-choose v-if="stationChooseVisible" @when-choose="whenStationChoose" />
</el-dialog>
弹窗样式控制:
/* 弹层高度固定 80vh */
/deep/ .fixed-dialog .el-dialog__body {
height: calc(70vh + 60px);
/*max-height: 80vh;*/
overflow-y: auto;
}
/* 弹层顶部间距固定 5vh */
/deep/ .fixed-dialog.el-dialog {
margin-top: 5vh !important;
}
方法块:
whenStationChoose(stationNo) {
this.queryForm.stationNo = stationNo
this.stationChooseVisible = false
},
openStationChoose() {
this.stationChooseVisible = true
},
点位选取组件:
<template>
<div id="choosePanel">
<div id="container" /> <div id="chooseForm">
<el-form :ref="chooseFormRef" :model="chooseForm" :rules="chooseRules" inline>
<el-form-item label="所在区域" size="small">
<el-cascader
v-model="chooseForm.regionCode"
size="small"
:options="regionTree"
:props="regionProps"
placeholder="所在区域"
clearable
@change="handleRegionChange"
/>
</el-form-item> <el-form-item label="站点状态" size="small">
<el-select v-model="chooseForm.runStatus" placeholder="请选择" clearable size="small">
<el-option v-for="item in runStatusList" :key="item.diCode" :label="item.diName" :value="item.diCode" />
</el-select>
</el-form-item> <el-form-item label="站点名称" size="small">
<el-input v-model.trim="chooseForm.stationName" clearable placeholder="请输入站点名称" size="small" />
</el-form-item> <el-form-item label="站点编号" size="small">
<el-input v-model.trim="chooseForm.stationNo" clearable placeholder="请输入站点编号" size="small" />
</el-form-item> <el-form-item size="small">
<el-button type="primary" size="small" @click="chooseSubmit">查询</el-button>
<el-button size="small" @click="chooseReset">重置</el-button>
</el-form-item>
</el-form> <div v-show="resultVisible" id="resultList">
<span>查询结果共有 {{ resultList.length }} 条 <i class="el-icon-close result-close-btn" @click="resultVisible = false" /></span>
<div v-for="(result, idx) in resultList" :key="`result${idx}`" class="result-info">
<i class="result-seq">{{ idx + 1 }}</i>
<div class="result-link">
<div>
<el-link type="primary" @click="locateStationInMap(result)">
{{ result.stationName }} ({{ result['stationLocation'] }})
</el-link>
</div>
<div style="color: #8C8C8C; margin-top: 5px;">{{ result.stationNo }}</div>
</div>
</div>
</div>
</div> </div>
</template> <script>
import { getSysRegionCascadeList } from '../../api/perception/basic/region'
import { getAllStation, getStationListByChoose } from '../../api/perception/basic/station' export default {
name: 'StationChoose',
data() {
return {
stationIconOnline: require('@/assets/image/station-online.png'),
stationIconOffline: require('@/assets/image/station-offline.png'),
mapInstance: null,
currentPopup: null,
stationList: [],
runStatusList: [
{ diCode: '', diName: '全部' },
{ diCode: 1, diName: '在线' },
{ diCode: -1, diName: '离线' }
],
chooseFormRef: 'chooseFormRefKey',
chooseRules: {},
chooseForm: {
regionCode: '',
runStatus: '',
stationName: '',
stationNo: ''
},
resultList: [],
resultVisible: true,
regionTree: [],
regionProps: {
label: 'regionName',
value: 'regionCode',
children: 'subRegions',
checkStrictly: true,
emitPath: false,
lazy: true,
async lazyLoad(node, resolve) {
if (node.root) {
const { data: regionTree } = await getSysRegionCascadeList({ levelNo: 0 })
resolve(regionTree)
} else {
const { data: subRegions } = await getSysRegionCascadeList({ parentCode: node.data.regionCode })
if (subRegions && subRegions.length > 0) {
const mapList = subRegions.map(x => ({ ... x, leaf: x.levelNo === 2 }))
return resolve(mapList)
} else return resolve([])
}
}
}
}
},
mounted() {
this.mapInitialize()
},
methods: {
handleRegionChange(val) {},
async mapInitialize() {
window.minemap.domainUrl = window._config.minemapDomainUrl
window.minemap.dataDomainUrl = window._config.minemapDataDomainUrl
window.minemap.serverDomainUrl = window._config.minemapServerDomainUrl
window.minemap.spriteUrl = window._config.minemapSpriteUrl
window.minemap.serviceUrl = window._config.minemapServiceUrl
window.minemap.key = window._config.minemapKey
window.minemap.solution = window._config.minemapWhiteSolution this.mapInstance = new window.minemap.Map({
container: 'container',
style: `${window._config.minemapSolutionUrl}/${window.minemap.solution}`,
center: [window._config.MAP_LNG, window._config.MAP_LAT],
zoom: 8,
pitch: 0,
projection: window.minemap.ProjectionType.LATLON // 经纬度投影
}) /* 添加控件 */
const mineMapScale = new window.minemap.Scale()
const mineMapNavigation = new window.minemap.Navigation()
this.mapInstance.addControl(mineMapScale, 'bottom-right')
this.mapInstance.addControl(mineMapNavigation, 'top-right') await this.initialAllStation()
},
async initialAllStation() {
const { data: stationList } = await getAllStation()
this.stationList = stationList
stationList.forEach(station => {
this.addStationMarker(station)
})
},
addStationMarker(station) {
const isOnline = station['runStatus'] === 1
const markerDom = window.document.createElement('div')
markerDom.id = `station${station.id}`
markerDom.style['background-image'] = `url(${isOnline ? this.stationIconOnline : this.stationIconOffline})`
markerDom.style.width = '32px'
markerDom.style.height = '30px'
markerDom.style['border-radius'] = '2px' const mineMarker = new window.minemap.Marker(markerDom, { offset: [-16, -30] })
const markerLngLat = new window.minemap.LngLat(station['longitudeGps'], station['latitudeGps'])
mineMarker.setLngLat(markerLngLat)
mineMarker.addTo(this.mapInstance) markerDom.addEventListener('click', event => {
this.mapInstance.panTo([station['longitudeGps'], station['latitudeGps']])
this.addStationPopup(station)
})
},
addStationPopup(station) {
if (this.currentPopup) this.currentPopup.remove()
this.currentPopup = null
const isOnline = station['runStatus'] === 1
const markerLngLat = new window.minemap.LngLat(station['longitudeGps'], station['latitudeGps'])
// 参数说明:
// (1)closeButton,true表示会展示一个关闭按钮;
// (2)closeOnClick,设置为true表示当地图被点击时该信息窗体会被关闭;
// (3)offset,参数为点位置相对于其左上角偏移像素大小;
// (4)anchor,停靠点,值域为[top,bottom,left,right,top-left,top-right,bottom-left,bottom-right],如果不设置该参数,则会根据map container动态调整。
// (5)autoPan,设置为true时,当地图拖动到看不到popup的时候,自动将地图平移到可以看到popup,此参数只对固定popup有效;
const stationPopup = new window.minemap.Popup({
closeOnClick: false,
closeButton: true,
anchor: 'bottom',
offset: [-1, -35],
autoPan: false
})
stationPopup.setLngLat(markerLngLat)
/* 设置弹窗样式 */
const popupContent = window.document.createElement('div')
popupContent.className = isOnline ? 'station-popup online' : 'station-popup offline'
popupContent.innerHTML = `
<h3>${station.stationName} ${station['stationLocation']}</h3>
<p>区域编码:<span>${station.regionCode}</span></p>
<p>站点编号:<span>${station.stationNo}</span></p>
<p>站点状态:<span>${station['runStatus'] === 1 ? '在线' : '离线'}</span></p>
<div class="choose-btn" id="${station.stationNo}">选定</div>
`
stationPopup.setDOMContent(popupContent)
stationPopup.addTo(this.mapInstance) /* 默认不打开弹窗 */
// mineMarker.setPopup(stationPopup) const btn = document.getElementById(station.stationNo)
btn.addEventListener('click', event => {
console.log('事件触发,发射数据 ' + station.stationNo)
this.$emit('when-choose', station.stationNo)
}) this.currentPopup = stationPopup
},
chooseSubmit() {
this.$refs[this.chooseFormRef].validate(async(valid) => {
if (!valid) return
const withoutName = !this.chooseForm.stationName
const withoutNo = !this.chooseForm.stationNo
if (withoutName && withoutNo) return this.$message.warning('站点名称和站点编号不能同时为空') const { data: resultList } = await getStationListByChoose(this.chooseForm)
this.resultList = resultList
this.resultVisible = true
})
},
chooseReset() {
this.resultVisible = false
this.chooseForm = {
regionCode: '',
runStatus: '',
stationName: '',
stationNo: ''
}
},
locateStationInMap(station) {
this.mapInstance.panTo([station['longitudeGps'], station['latitudeGps']])
this.addStationPopup(station)
}
}
}
</script> <style scoped>
#choosePanel {
width: 100%;
height: 100%;
background-color: white;
position: relative;
}
#container {
width: 100%;
height: 100%;
}
#chooseForm {
width: 90%;
position: absolute;
top: 20px;
left: 20px;
padding: 10px 10px 0 10px;
background-color: white;
box-shadow: 0 2px 4px #9e9e9ec2;
}
#resultList {
position: absolute;
bottom: 0;
right: 0;
transform: translateY(110%);
background: #fff;
box-shadow: 0 0 4px #9e9e9ec2;
padding: 8px;
width: 400px;
}
.result-close-btn {
float: right;
font-size: 16px;
cursor: pointer;
}
.result-info {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
margin: 15px 0;
}
.result-seq {
display: block;
font-style: italic;
width: 15px;
font-weight: bolder;
}
.result-link {
width: 365px;
text-align: left;
}
/* 替换回原本颜色 */
/deep/ .el-form-item__label {
color: #666666 !important;
}
/deep/ #chooseForm .el-input__inner {
color: #666666 !important;
border-color: #CBCFD5 !important;
border-radius: 0 !important;
}
/deep/ #chooseForm .el-input__inner:hover, .el-textarea__inner:hover {
border-color: #CBCFD5 !important;
}
/deep/ #chooseForm .el-range-input {
background-color: transparent;
color: rgba(255, 255, 255, 0.6) !important;
} /* 地图样式 */
/* mineMap地图样式设置 */
/deep/ .minemap-popup-content {
background: rgb(10,76,150,.9) !important;
min-height: 100px;
padding-top: 10px;
overflow-y: auto;
min-width: 200px;
max-width: 500px !important;
color: #D0D3DA;
} /* 关闭按钮调整 */
/deep/ .minemap-popup-close-button {
font-size: 20px;
color: #D0D3DA;
right: 8px;
top: 6px;
}
/* 移除默认的箭头 */
/deep/ .minemap-popup-tip {
/*background: rgb(10,76,150,.7) !important;*/
}
/deep/ .station-popup {
padding: 5px;
width: 250px;
font-size: 14px;
}
/deep/ .station-popup > .choose-btn {
cursor: pointer;
background: #46a6ff;
color: #fff;
text-align: center;
border-radius: 2px;
padding: 4px 2px 2px 2px;
} /deep/ .station-popup > p,
/deep/ .station-popup > .choose-btn {
margin-top: 10px;
} /deep/ .station-popup > p > span,
/deep/ .station-popup > h3 {
color: white !important;
}
.zdgk-statistic-val {
color: #f1ba05;
font-size: 12px;
font-weight: bold;
}
/deep/.minemap-popup-anchor-bottom .minemap-popup-tip {
border-top-color: rgb(10, 76, 150, 0.9) !important;
}
</style>
其实主要难点是在点位信息窗体上加事件传递信息,这个是手写的dom元素,不会给vue去绑定处理
没有啥好办法,就直接用id属性设置站点编号,然后加个点击事件来触发方法
调用发射器给业务组件用
2023年12月13日更新内容:
1、真实路线需求
产品对线图层的效果不满意,因为无法得知真实路线的走线情况
这里同事追加了一个细腻点位的接口,传入原始点位集合,返回真实路线的点位集合
如果使用投射参数偏移了定位,路线是无效的,因为要依据地图路线映射
效果预览:
接口地址:
minemapCarLocus: 'https://service.minedata.cn/service/lbs/route/v1/driving?key=#####',
调用方法:
getCarPath: async function (path) {
let realPath = []
let lastRoute = path
for (; lastRoute.length > 0;) {
const first = lastRoute[0]
let last = ''
const waypoints = []
if (lastRoute.length > 52) {
last = lastRoute[51]
for (let i = 1; i < 51; i++) {
waypoints.push(lastRoute[i])
}
lastRoute = lastRoute.slice(52, lastRoute.length)
} else {
last = lastRoute[lastRoute.length - 1]
for (let i = 1; i < lastRoute.length - 1; i++) {
waypoints.push(lastRoute[i])
}
lastRoute = []
}
/* 调用四维接口获取真实路径轨迹 */
const { data: data } = await window.$Axios.get(window._config.minemapCarLocus + '&origin=' + first + '&destination=' + last + '&waypoints=' + waypoints.join(';'))
const steps = data.result.routes[0].steps
steps.forEach(e => {
const polyline = e.polyline
if (polyline) {
const polylines = polyline.split(';')
polylines.forEach(loc => {
const locs = loc.split(',')
realPath.push([locs[0], locs[1]])
})
}
})
}
return realPath;
}
2、解决投射参数配置问题:
/**
* 是否使用投影参数
* http://113.108.157.29:7070/support/static/api/doc/js/v2.1.0/api-reference/index.html#map
* true -> projection: window.minemap.ProjectionType.LATLON
* false -> projection: window.minemap.ProjectionType.MERCATOR
*/
projectionFlag: false,
所有地图初始化之前访问flag变量检查是否启用投射
因为线上环境需要投射,但是本地环境和测试环境不需要投射展示
this.highwayMapInstance = new window.minemap.Map({
container: 'highwayMapContainer',
style: `${window._config.minemapSolutionUrl}/${window._config.minemapBlackSolution}`,
center: [window._config.MAP_LNG, window._config.MAP_LAT],
zoom: 12,
pitch: 0,
projection: window._config.projectionFlag ? 'LATLON' : 'MERCATOR' // 经纬度投影
})
2023年12月21日 更新:
新增了一个地图绘制布控区域的需求
- 1、用户可以在地图上绘制一片区域,并输入文本表示是什么区域
- 2、区域回显之后,光标悬浮区域时,显示信息窗体,展示该区域内的站点统计信息
先看图层编辑的案例:
https://minedata.com.cn/support/api/demo/js-api/zh/edit/data/data-add
提供了一套编辑的API和交互案例
但是案例没有说清楚是怎么读取编辑的图层对象
然后在注释里找到了:
http://113.108.157.29:7070/minemapapi/v2.1.0/plugins/edit/api.html
因为不关心编辑时如何,只要拿到所有编辑好的图层信息即可
要回显之前编辑的图层,用案例的setFeatures的API
但是一定要放到地图的加载周期里面:
因为有多用户操作布控区域的可能,所以要区分用户,表结构:
CREATE TABLE `bk_features` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '记录主键',
`features` text COLLATE utf8mb4_general_ci COMMENT '布控区域矢量信息',
`creator` varchar(32) COLLATE utf8mb4_general_ci NOT NULL COMMENT '创建人',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='布控区域记录表';
业务逻辑暂时想的简单,无非是查询用户有没有编辑图层信息
有就回显,如果重复编辑就更新
/**
* @author OnCloud9
* @version 1.0
* @project road-perception-server
* @date 2023年12月18日 19:17
*/
@Service("bkFeaturesService")
public class BkFeaturesServiceImpl extends ServiceImpl<BkFeaturesDAO, BkFeaturesDTO> implements IBkFeaturesService {
@Override
public void saveCurrentFeatures(BkFeaturesDTO dto) {
UserContext user = LoginUserContext.getUser();
String id = user.getId();
dto.setCreator(id);
dto.setCreateTime(new Date());
List<BkFeaturesDTO> list = lambdaQuery().eq(BkFeaturesDTO::getCreator, id).list();
boolean hasOne = CollectionUtils.isNotEmpty(list);
if (hasOne) {
BkFeaturesDTO one = list.get(0);
one.setFeatures(dto.getFeatures());
baseMapper.updateById(one);
} else {
baseMapper.insert(dto);
}
}
@Override
public BkFeaturesDTO getCurrentFeatures() {
UserContext user = LoginUserContext.getUser();
String id = user.getId();
List<BkFeaturesDTO> list = lambdaQuery().eq(BkFeaturesDTO::getCreator, id).list();
return CollectionUtils.isEmpty(list) ? null : list.get(0);
}
}
控制器:
因为保存前端提交的json数据不被转义,就加上转义逃逸注解处理
/**
* @author OnCloud9
* @version 1.0
* @project road-perception-server
* @date 2023年12月18日 19:18
*/
@RestController
@RequestMapping("${api.path}/features")
public class BkFeaturesController extends BaseController { @Resource
private IBkFeaturesService bkFeaturesService; /**
* @author OnCloud9
* @date 2023/12/18 19:21
* @description
* @params [dto]
* @return void
*/
@ActiveDeXss
@PostMapping("/save")
public void saveCurrentFeatures(@RequestBody BkFeaturesDTO dto) {
bkFeaturesService.saveCurrentFeatures(dto);
} @GetMapping("/current")
public BkFeaturesDTO getCurrentFeatures() {
return bkFeaturesService.getCurrentFeatures();
}
}
地图前端方法:
1、初始化处理
async initialMapContainer() {
window.minemap.domainUrl = window._config.minemapDomainUrl
window.minemap.dataDomainUrl = window._config.minemapDataDomainUrl
window.minemap.serverDomainUrl = window._config.minemapServerDomainUrl
window.minemap.spriteUrl = window._config.minemapSpriteUrl
window.minemap.serviceUrl = window._config.minemapServiceUrl
window.minemap.key = window._config.minemapKey
window.minemap.solution = window._config.minemapBlackSolution this.mapInstance = new window.minemap.Map({
container: 'mapContainer',
style: `${window._config.minemapSolutionUrl}/${window._config.minemapBlackSolution}`,
center: [window._config.MAP_LNG, window._config.MAP_LAT],
zoom: 12,
pitch: 0,
projection: window._config.projectionFlag ? 'LATLON' : 'MERCATOR' // 经纬度投影
})
this.initialMapEditor() this.mapInstance.on('load', () => {
// 向编辑池增加数据
this.initialMapFeatures()
})
this.initialAllStation()
},
async initialMapFeatures() {
const { data } = await getCurrentFeatures()
console.log(data)
if (!data) return
const featuresCollection = JSON.parse(data['features'])
console.log(featuresCollection)
if (!featuresCollection) return
/* 回显编辑内容 */
this.mapEditor.setFeatures(featuresCollection)
},
2、保存方法:
saveFeatures() {
const allFeatures = this.mapEditor.draw.getAll()
saveCurrentFeatures({ features: JSON.stringify(allFeatures) })
this.$message.success('保存成功')
},
3、文本编辑问题
因为案例是写死的,这个肯定要交给用户输入处理
文本输入弹窗
<el-dialog title="添加文字" width="400px" :visible.sync="textVisible" :close-on-click-modal="false" append-to-body>
<el-form size="small">
<el-form-item>
<el-input v-model.trim="featureText" />
</el-form-item>
<el-form-item align="center">
<el-button type="primary" @click="featureTextConfirm">确定</el-button>
<el-button type="default" @click="textVisible = false">取消</el-button>
</el-form-item>
</el-form>
</el-dialog>
确认文本后再触发编辑器交互:
featureTextConfirm() {
if (!this.featureText) return this.$message.error('请填写文字内容')
this.textVisible = false
this.mapEditor.onBtnCtrlActive('text', {
style: {
'textField': this.featureText + '',
'textColor': '#00FF00',
'textSize': 24,
'custom_style': 'true' // 此参数为string类型
}
})
},
4、回显展示图层的问题:
因为首页和编辑功能不一样,这里需要做图层转换处理
面图层和图标图层的绘制方式不一样,我们先用分组方法进行分组处理:
/* 分为面图层渲染(el.geometry.type 'Polygon')和图标图层渲染(el.geometry.type 'Point') */
const fcTypeMap = groupBy(featureCollection['features'], el => el.geometry.type)
const polygonList = fcTypeMap['Polygon']
const pointList = fcTypeMap['Point']
分组方法仿照Java的Stream方法实现:
/**
* 集合分组方法
* @param collect
* @param groupFun
*/
const groupBy = (collect, groupFun) => {
return collect.reduce((groupedMap, el) => {
const keyVal = groupFun(el)
if (!keyVal) return groupedMap
if (!groupedMap[keyVal]) groupedMap[keyVal] = []
groupedMap[keyVal].push(el)
return groupedMap
}, {})
}
面图层相对简单,直接丢进去就可以了
因为可能绘制多个区域,每个区域追加点击事件,所以这样循环添加Source和Layer
方便后面的事件绑定处理,
Source和Layer的逻辑还是比较好理解的,现有Source矢量信息,然后Layer创建时绑定Source信息
而Feature对象是编辑器edit对象创建的,可以交给Source转换
Feature对象的id创建时自动分配,应该是唯一的
这里可以把 Source-Id, Layer-Id Feature-Id 统一使用,方便维护
polygonList.forEach((el, idx) => {
const featureId = el.id
const coordinates = el.geometry.coordinates
this.mapInstance.addSource(featureId, {
type: 'geojson',
data: { type: 'FeatureCollection', features: [el] }
}) this.mapInstance.addLayer({
'id': featureId,
'type': 'fill',
'source': featureId,
'layout': { 'visibility': 'visible' },
'paint': {
'fill-color': '#00ff00',
'fill-opacity': 0.4,
'fill-outline-color': '#ff0000'
},
'minzoom': 7,
'maxzoom': 17.5
})
})
给图层绑定鼠标事件,鼠标进入,鼠标离开两个事件
https://minedata.com.cn/support/static/api/doc/js/v2.1.0/api-reference/index.html#mapon
但是要统计绘制区域内的站点信息,就要知道我们的打的站点是否在这个区域内,这个API正好还被我找到了哈哈哈
http://113.108.157.29:7070/support/api/demo/js-cmpt/zh/geometry/relation/point-in-polygon
我找到的这个是带了一个地图工具库对象的方法
默认的案例中是使用turf对象判断的,还需要转换coordinates信息
http://113.108.157.29:7070/support/api/demo/js-api/zh/geometry/relation/point-in-polygon
这里挂载事件的时候和上面说的一样,直接丢featureId进来
this.mapInstance.on('mouseenter', featureId, async event => {
console.log(`鼠标移入触发! ${featureId} ${event.lngLat.lng} ${event.lngLat.lat}`)
if (this.currentFeaturePopup) this.currentFeaturePopup.remove() /* 计算站点信息统计 */
const featureStations = []
this.allStations.forEach(station => {
const stationCoord = [station['longitudeGps'], station['latitudeGps']]
const isInPolygon = window.minemaputil.SpaceUtil.pointWithinPolygon(stationCoord, coordinates)
if (isInPolygon) featureStations.push(station)
}) /* 统计设备信息 */
const deviceCount = featureStations.length
const onlineCount = featureStations.filter(s => s['runStatus'] === 1).length
const offlineCount = featureStations.filter(s => s['runStatus'] !== 1).length /* 统计在线站点采集数 */
const stationNoList = featureStations.map(x => x.stationNo)
let carCount = 0
let bleCount = 0
if (deviceCount !== 0) {
const { data } = await getFeaturesAreaOverView({ stationNoList: stationNoList })
carCount = data['carCount']
bleCount = data['bleCount']
} /* 区域名称提取 */
const target = pointList.find(p => window.minemaputil.SpaceUtil.pointWithinPolygon(p.geometry.coordinates, coordinates))
let title = target.properties.textField
title = title || '未命名' /* 设置弹窗样式 */
const popupContent = window.document.createElement('div')
popupContent.className = 'station-popup'
popupContent.innerHTML = `
<h3>${title}</h3>
<p>设备:${deviceCount}</p>
<p>在线:${onlineCount}</p>
<p>离线:${offlineCount}</p>
<p>车辆 ${carCount}, 蓝牙 ${bleCount}</p>
` const popup = new window.minemap.Popup({
closeOnClick: true,
closeButton: false,
anchor: 'bottom-left',
offset: [5, 10],
autoPan: false
})
popup.setDOMContent(popupContent)
popup.trackPointer() // 此方法可以让popup跟随鼠标指针,使用此方法就不再需要使用setLngLat()方法
popup.addTo(this.mapInstance)
this.currentFeaturePopup = popup
})
this.mapInstance.on('mouseleave', featureId, event => {
console.log(`鼠标移触发触发! ${featureId}`)
/* 如果存在,删除这个弹窗 */
if (this.currentFeaturePopup) this.currentFeaturePopup.remove()
})
图标图层渲染文字时,发现它是带了一个种类,有个锚点图标
案例地址:
https://minedata.com.cn/support/api/demo/js-api/zh/layer/base/symbol-layer
这个事情问了下维护人员,他说你就删掉icon信息就可以了.... (这里可以对比我的代码和案例代码)
但是feature对象不能直接套用,要按照demo的结构重新构造一遍,麻烦
pointList.forEach(el => {
const featureId = el.id
/* 原feature对象无法适配,创建一个适配的feature对象 */
const adaptFeature = {
id: featureId,
type: 'Feature',
geometry: el.geometry,
properties: {
title: el.properties.textField,
kind: 'label'
}
}
this.mapInstance.addSource(featureId, {
type: 'geojson',
data: { type: 'FeatureCollection', features: [adaptFeature] }
}) this.mapInstance.addLayer({
'id': featureId,
'type': 'symbol',
'source': featureId,
'layout': {
'visibility': 'visible',
'text-field': '{title}',
'text-offset': [0, 0.6],
'text-anchor': 'top',
'text-size': 24,
'icon-allow-overlap': true, // 图标允许压盖
'text-allow-overlap': true // 图标覆盖文字允许压盖
},
'paint': {
'text-color': {
'type': 'categorical',
'property': 'kind',
'stops': [['label', '#ff0000'], ['park', '#00ff00'], ['hospital', '#0000ff']],
'default': '#ff0000'
},
'text-halo-color': '#000000',
'text-halo-width': 0.5
},
'minzoom': 7,
'maxzoom': 17.5
})
})
实现效果:
2023年12月25日 更新:
下一步是样式设置的问题,弹窗的样式设置在上面有提到过
但是图层样式没有,这个得自己找找了
问题一:面图层的边框线条只能设置颜色
面图层样式参数文档:
https://minedata.com.cn/support/static/api/doc/js/v2.1.0/style-specification/index.html#/layers/fill/*
但是产品的原型效果是这样的
于是继续问那边的维护人员,解决方案的话是在面图层的基础上添加一个线图层来实现
虚线的实现就是多加了一个虚线数组参数: 'line-dasharray': [1, 4],
/**
* 线图层绘制方法
*/
drawLineLayer(layerId, coordinates) {
/* 额外追加线图层来设置样式 */
this.mapInstance.addSource(layerId, {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': { 'type': 'LineString', 'coordinates': coordinates },
'properties': { 'title': '路线一', 'kind': 1 }
}]
}
})
this.mapInstance.addLayer({
'id': layerId,
'type': 'line',
'source': layerId,
'layout': {
'line-join': 'round',
'line-cap': 'round',
'border-visibility': 'visible' // 是否开启线边框
},
'paint': {
'line-width': 6,
'line-dasharray': [1, 4],
'line-color': {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#02F4FF'], [2, '#02F4FF']],
'default': '#02F4FF'
},
'line-border-width': 2, // 设置线边框宽度
'line-border-opacity': 1, // 设置线边框透明度
'line-border-color': 'blue' // 设置线边框颜色
},
'minzoom': 7,
'maxzoom': 17.5
})
},
面图层的坐标信息不能直接挪用到线图层去,因为面图层的坐标是三维数组,但是线图层是二维数组
问题二:样式切换问题
参考图层样式设置demo:
https://minedata.com.cn/support/api/demo/js-api/zh/layer/manage/layer-opacity-setting
之前有提到利用鼠标监听事件来实现,所以设置一定是在监听事件中执行的
async initialEditorFeature() {
const { data } = await getCurrentFeatures()
if (!data) return
const featureCollection = JSON.parse(data['features'])
if (!featureCollection) return /* 分为面图层渲染(el.geometry.type 'Polygon')和图标图层渲染(el.geometry.type 'Point') */
const fcTypeMap = groupBy(featureCollection['features'], el => el.geometry.type)
const polygonList = fcTypeMap['Polygon']
const pointList = fcTypeMap['Point'] polygonList.forEach((el, idx) => {
const featureId = el.id
const lineId = `line-${featureId}`
const pointId = `point-${featureId}`
const coordinates = el.geometry.coordinates
console.log(`coordinates ${JSON.stringify(coordinates)}`)
const pointTarget = pointList.find(p => window.minemaputil.SpaceUtil.pointWithinPolygon(p.geometry.coordinates, coordinates)) /* 原feature对象无法适配,创建一个适配的feature对象 */
const adaptFeature = {
id: pointId,
type: 'Feature',
geometry: pointTarget.geometry,
properties: {
title: pointTarget.properties.textField,
kind: 'label'
}
} this.drawLineLayer(lineId, coordinates[0])
this.drawPolygonLayer(featureId, el)
this.drawPointLayer(pointId, adaptFeature) this.mapInstance.on('mouseenter', featureId, async event => {
if (this.currentFeaturePopup) this.currentFeaturePopup.remove() const featureInfo = await this.getFeatureStationsInfo(coordinates, pointTarget)
this.openFeatureInfoWindow(featureInfo) /* 设置激活的Layer样式 */
this.mapInstance.setPaintProperty(featureId, 'fill-color', '#FFEA59')
this.mapInstance.setLayoutProperty(pointId, 'visibility', 'none')
this.mapInstance.setPaintProperty(lineId, 'line-color', {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#FFEA59'], [2, '#FFEA59']],
'default': '#02F4FF'
})
}) this.mapInstance.on('mouseleave', featureId, event => {
/* 如果存在,删除这个弹窗 */
if (this.currentFeaturePopup) this.currentFeaturePopup.remove() /* 恢复默认的Layer样式 */
this.mapInstance.setPaintProperty(featureId, 'fill-color', '#059ded')
this.mapInstance.setLayoutProperty(pointId, 'visibility', 'visible')
this.mapInstance.setPaintProperty(lineId, 'line-color', {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#02F4FF'], [2, '#02F4FF']],
'default': '#02F4FF'
})
})
})
},
拆分出来的方法代码:
openFeatureInfoWindow(featureInfo) {
/* 设置弹窗样式 */
const popupContent = window.document.createElement('div')
popupContent.className = 'bk-area-popup'
popupContent.innerHTML = `
<h3>${featureInfo.title}</h3>
<p>设备:${featureInfo.deviceCount} 在线:${featureInfo.onlineCount} 离线:${featureInfo.offlineCount}</p>
<p>车辆:${featureInfo.carCount} 蓝牙:${featureInfo.bleCount}</p>
` const popup = new window.minemap.Popup({
closeOnClick: true,
closeButton: false,
anchor: 'bottom-left',
offset: [5, 0],
autoPan: false
})
popup.setDOMContent(popupContent)
popup.trackPointer() // 此方法可以让popup跟随鼠标指针,使用此方法就不再需要使用setLngLat()方法
popup.addTo(this.mapInstance)
this.currentFeaturePopup = popup
},
/**
* 获取弹窗信息
*/
async getFeatureStationsInfo(rangeCoordinates, pointTarget) {
/* 1、计算站点信息统计 */
const featureStations = []
this.allStations.forEach(station => {
const stationCoord = [station['longitudeGps'], station['latitudeGps']]
const isInPolygon = window.minemaputil.SpaceUtil.pointWithinPolygon(stationCoord, rangeCoordinates)
if (isInPolygon) featureStations.push(station)
}) /* 2、统计设备信息 */
const deviceCount = featureStations.length
const onlineCount = featureStations.filter(s => s['runStatus'] === 1).length
const offlineCount = featureStations.filter(s => s['runStatus'] !== 1).length /* 3、统计在线站点采集数 */
const stationNoList = featureStations.map(x => x.stationNo)
let carCount = 0
let bleCount = 0
if (deviceCount !== 0) {
const { data } = await getFeaturesAreaOverView({ stationNoList: stationNoList })
carCount = data['carCount']
bleCount = data['bleCount']
} /* 4、区域名称提取 */
let title = pointTarget.properties.textField
title = title || '未命名' return {
deviceCount,
onlineCount,
offlineCount,
carCount,
bleCount,
title,
pointTarget
}
},
/**
* 面(多边形)图层绘制方法
*/
drawPolygonLayer(layerId, featureObj) {
this.mapInstance.addSource(layerId, {
type: 'geojson',
data: { type: 'FeatureCollection', features: [featureObj] }
}) this.mapInstance.addLayer({
'id': layerId,
'type': 'fill',
'source': layerId,
'layout': { 'visibility': 'visible' },
'paint': {
'fill-antialias': true, /* 启用抗锯齿 */
'fill-color': '#059ded',
'fill-opacity': 0.4,
'fill-outline-color': '#059ded'
},
'minzoom': 7,
'maxzoom': 17.5
})
},
/**
* 线图层绘制方法
*/
drawLineLayer(layerId, coordinates) {
/* 额外追加线图层来设置样式 */
this.mapInstance.addSource(layerId, {
'type': 'geojson',
'data': {
'type': 'FeatureCollection',
'features': [{
'type': 'Feature',
'geometry': { 'type': 'LineString', 'coordinates': coordinates },
'properties': { 'title': '路线一', 'kind': 1 }
}]
}
})
this.mapInstance.addLayer({
'id': layerId,
'type': 'line',
'source': layerId,
'layout': {
'line-join': 'round',
'line-cap': 'round',
'border-visibility': 'visible' // 是否开启线边框
},
'paint': {
'line-width': 6,
'line-dasharray': [1, 4],
'line-color': {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#02F4FF'], [2, '#02F4FF']],
'default': '#02F4FF'
},
'line-border-width': 2, // 设置线边框宽度
'line-border-opacity': 1, // 设置线边框透明度
'line-border-color': 'blue' // 设置线边框颜色
},
'minzoom': 7,
'maxzoom': 17.5
})
},
drawPointLayer(layerId, featureObj) {
this.mapInstance.addSource(layerId, {
type: 'geojson',
data: { type: 'FeatureCollection', features: [featureObj] }
}) this.mapInstance.addLayer({
'id': layerId,
'type': 'symbol',
'source': layerId,
'layout': {
'visibility': 'visible',
'text-field': '{title}',
'text-offset': [0, 0.6],
'text-anchor': 'top',
'text-size': 24,
'icon-allow-overlap': true, // 图标允许压盖
'text-allow-overlap': true // 图标覆盖文字允许压盖
},
'paint': {
'text-color': {
'type': 'categorical',
'property': 'kind',
'stops': [['label', '#48D4FF'], ['park', '#fff'], ['hospital', '#fff']],
'default': '#fff'
},
'text-halo-color': '#fff',
'text-halo-width': 0.2
},
'minzoom': 7,
'maxzoom': 17.5
})
},
这里涉及到了一个深度对象设置参数的问题,但是我懒得解决了,直接整个对象丢进来
那边是提供了一个表达式文档,可以参考看看:
https://minedata.com.cn/support/static/api/doc/js/v2.1.0/style-specification/index.html#/expressions
效果实现:
悬停在绘制区域内时
‘
脱离悬停区域时:
目前来说对这个效果还算满意哈哈哈
2023年12月26日更新:
昨天还遗留了这个问题,就是跟随光标的弹窗留了一个箭头样式没修复
之前我们设置的样式是这么处理的:
/deep/.minemap-popup-anchor-bottom > .minemap-popup-tip {
border-top-color: rgba(10,76,150,0.9) !important;
}
对应的achor参数是bottom,但是这个光标跟随的是bottom-left
我就想应该是对应的一个class类名,试了下还真是这样
悬浮样式的箭头是不需要的,所以这个边框头直接不要任何样式
/* 悬浮弹窗箭头样式移除 */
/deep/.minemap-popup-anchor-bottom-left > .minemap-popup-tip {
border-top: none !important;
}
这样就修好了
2024年01月22日更新:
因为存在编辑图层交叉区域的情况,还存在打点信息交互的点图层
这里就出现了悬浮区域信息展示的BUG,会多次重复展示,直接进入交叉区域会将两个区域的信息重叠展示
之前是以为只有一个区域弹窗信息出现:
currentFeaturePopup: null
那么现在我们改为:
currentFeaturePopup: []
清除弹窗信息方法:
之前是用forEach处理的好像,这里老老实实用api的方式处理
clearFeaturePopup() {
for (let i = 0; i < this.currentFeaturePopup.length; i++) this.currentFeaturePopup[i].remove()
this.currentFeaturePopup = []
},
所有涉及地图交互的事件,建议调用清除方法:
因为首页只有一个区域和点位展示,这里打开点位时的事件追加清除方法
包括鼠标移入移出这个点位也要清除
处理交叉区域的事件,通过console打印发现,交叉区域会同时触发两个图层的事件
无法避免的,所以这里我补加了一个之后的判断,检查弹层是不是超出1个,不是的话只保留最新的那个
2024年01月30日更新:
1、失焦BUG问题
产品测试发现展示弹窗的时候如果光标处在图层的边界位置时,弹窗的坐标定位失焦
这个bug在去年就提了,我改了好几个事件的处理都发现没有用,最后定位是这个API有BUG
要不要处理就看产品和测试要求了,我个人认为是能接受的
这里跟那边人员沟通写了demo看效果:
编辑器地址:
https://minedata.com.cn/support/api/demo/js-api/zh/layer/base/fill-layer
bug-demo.html 源码 (丢到上面的编辑器里面运行看效果):
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>面图层</title>
<link rel="stylesheet" href="//minedata.com.cn/support/static/api/demo/js-api/zh/css/demo.css">
<!-- 引入MineMap API插件 -->
<link rel="stylesheet" href="//minedata.com.cn/minemapapi/v2.1.0/minemap.css">
<script src="//minedata.com.cn/minemapapi/v2.1.0/minemap.js"></script>
<style>
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
/**
* 全局参数设置
*/
minemap.domainUrl = '//minedata.com.cn';
minemap.dataDomainUrl = '//minedata.com.cn';
minemap.serverDomainUrl = '//minedata.com.cn';
minemap.spriteUrl = '//minedata.com.cn/minemapapi/v2.1.0/sprite/sprite';
minemap.serviceUrl = '//minedata.com.cn/service/'; minemap.key = '16be596e00c44c86bb1569cb53424dc9';
minemap.solution = 12877;
var popups = [];
var map = new minemap.Map({
container: 'map',
style: '//minedata.com.cn/service/solu/style/id/12877',
center: [116.46,39.92],
zoom: 14,
pitch: 0,
maxZoom: 17,
minZoom: 3,
projection: 'MERCATOR'
}); /**
* 创建地图对象后,开始加载地图资源,地图资源加载完成后触发load事件
*/
map.on('load', function () {
// 增加自定义数据源、自定义图层
addSources();
addLayers();
addMouseEvent();
});
function addMouseEvent() {
var layerId = 'fillLayer'
map.on('mouseenter', layerId, function(event) {
// 参数说明:
// (1)maxWidth,默认值240px,popup容器的最大宽度,超过后内容将滚动出现;
var _popup = new minemap.Popup({closeOnClick: true, closeButton: false, anchor: 'top-left', offset: [15, 20], maxWidth: '100px'})
.setText("我是一个跟随鼠标指针信息窗体")
.trackPointer() //此方法可以让popup跟随鼠标指针,使用此方法就不再需要使用setLngLat()方法
.addTo(map);
popups.push(_popup);
})
map.on('mouseleave', layerId, function(event) {
for (let i = 0; i < popups.length; i++) popups[i].remove();
popups = []
})
} function addSources() {
var center = map.getCenter();
var jsonData = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[center.lng, center.lat], [center.lng, center.lat + 0.006], [center.lng - 0.006, center.lat + 0.006], [center.lng - 0.006, center.lat], [center.lng, center.lat]]]
}
}
]
};
map.addSource("fillSource", {
"type": "geojson",
"data": jsonData
});
} function addLayers() {
map.addLayer({
"id": "fillLayer",
"type": "fill",
"source": "fillSource",
"layout": {
"visibility": "visible",
},
"paint": {
"fill-color": "#00ff00",
"fill-opacity": 0.8,
"fill-outline-color": "#ff0000"
},
"minzoom": 7,
"maxzoom": 17.5
});
} </script>
</body>
bug效果:
2、解决方案
他那边给的建议时换成move事件监听,持续的刷新弹窗展示
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>面图层</title>
<link rel="stylesheet" href="http://minedata.com.cn/support/static/api/demo/js-api/zh/css/demo.css">
<!-- 引入MineMap API插件 -->
<link rel="stylesheet" href="http://minedata.com.cn/minemapapi/v2.1.0/minemap.css">
<script src="http://minedata.com.cn/minemapapi/v2.1.0/minemap.js"></script>
<style>
#map {
width: 100%;
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
/**
* 全局参数设置
*/
minemap.domainUrl = 'http://minedata.com.cn';
minemap.dataDomainUrl = 'http://minedata.com.cn';
minemap.serverDomainUrl = 'http://minedata.com.cn';
minemap.spriteUrl = 'http://minedata.com.cn/minemapapi/v2.1.0/sprite/sprite';
minemap.serviceUrl = 'http://minedata.com.cn/service/'; minemap.key = '16be596e00c44c86bb1569cb53424dc9';
minemap.solution = 12877;
var popups = [];
var map = new minemap.Map({
container: 'map',
style: 'http://minedata.com.cn/service/solu/style/id/12877',
center: [116.46,39.92],
zoom: 14,
pitch: 0,
maxZoom: 17,
minZoom: 3,
projection: 'MERCATOR'
}); /**
* 创建地图对象后,开始加载地图资源,地图资源加载完成后触发load事件
*/
map.on('load', function () {
// 增加自定义数据源、自定义图层
addSources();
addLayers();
addMouseEvent();
});
function addMouseEvent() {
var layerId = 'fillLayer'
map.on('mousemove', layerId, function(event) {
for (let i = 0; i < popups.length; i++) popups[i].remove();
popups = []
// 参数说明:
// (1)maxWidth,默认值240px,popup容器的最大宽度,超过后内容将滚动出现;
var _popup = new minemap.Popup({closeOnClick: true, closeButton: false, anchor: 'top-left', offset: [15, 20], maxWidth: '1000px'})
.setText("我是一个跟随鼠标指针信息窗体")
.setLngLat(event.lngLat)
//.trackPointer() //此方法可以让popup跟随鼠标指针,使用此方法就不再需要使用setLngLat()方法
.addTo(map);
popups.push(_popup);
console.log(popups.length)
})
map.on('mouseleave', layerId, function(event) {
for (let i = 0; i < popups.length; i++) popups[i].remove();
popups = []
})
} function addSources() {
var center = map.getCenter();
var jsonData = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[center.lng, center.lat], [center.lng, center.lat + 0.006], [center.lng - 0.006, center.lat + 0.006], [center.lng - 0.006, center.lat], [center.lng, center.lat]]]
}
}
]
};
map.addSource("fillSource", {
"type": "geojson",
"data": jsonData
});
} function addLayers() {
map.addLayer({
"id": "fillLayer",
"type": "fill",
"source": "fillSource",
"layout": {
"visibility": "visible",
},
"paint": {
"fill-color": "#00ff00",
"fill-opacity": 0.8,
"fill-outline-color": "#ff0000"
},
"minzoom": 7,
"maxzoom": 17.5
});
} </script>
</body>
3、数据加载的处理
我直接替换事件名称,改成事件源获取的坐标赋值处理弹窗
结果发现弹窗加载阻塞了.... 移动时会连续出现多个没关闭的弹窗
因为之前是enter进入事件,只有一次加载的时候
现在换成move事件会持续触发,请求就一直在执行,再加上async / await阻塞中持续
我感觉这个方式有点不对,然后猜测是接口响应阻塞导致的
那就这样,把接口的数据缓存起来,move事件有featureId挂着,那接口数据和featureId匹配即可
缓存池没找到这个区域的数据,就加载一次,放到池里面,然后渲染
下次触发时继续在池子找数据,不用再请求接口了
所以这个逻辑又拆成现在这段:
渲染的逻辑并不阻塞,效果是正常的了
this.mapInstance.on('mouseenter', featureId, async event => {
/* 设置激活的Layer样式 */
this.mapInstance.setPaintProperty(featureId, 'fill-color', '#FFEA59')
if (this.mapInstance.getLayer(pointId)) this.mapInstance.setLayoutProperty(pointId, 'visibility', 'none')
this.mapInstance.setPaintProperty(lineId, 'line-color', {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#FFEA59'], [2, '#FFEA59']],
'default': '#02F4FF'
})
}) this.mapInstance.on('mousemove', featureId, async event => {
this.clearFeaturePopup()
let featureInfo = this.featureWindowDataList.find(x => x['featureId'] === featureId)
if (!featureInfo) {
featureInfo = await this.getFeatureStationsInfo(coordinates, pointTarget)
featureInfo['featureId'] = featureId
this.featureWindowDataList.push(featureInfo)
}
this.openFeatureInfoWindow(featureInfo, event['lngLat']) /* 校验是否存在交叉区域 */
this.fixConflictFeaturePopup()
}) this.mapInstance.on('mouseleave', featureId, event => {
/* 如果存在,删除这个弹窗 */
this.clearFeaturePopup() /* 恢复默认的Layer样式 */
this.mapInstance.setPaintProperty(featureId, 'fill-color', '#059ded')
if (this.mapInstance.getLayer(pointId)) this.mapInstance.setLayoutProperty(pointId, 'visibility', 'visible')
this.mapInstance.setPaintProperty(lineId, 'line-color', {
'type': 'categorical',
'property': 'kind',
'stops': [[1, '#02F4FF'], [2, '#02F4FF']],
'default': '#02F4FF'
})
})
【Vue】MineData 地图接入的更多相关文章
- vue 高德地图之玩转周边
前言:在之前的博客中,有成功引入高德地图,这是以前的地址 vue 调用高德地图. 因为一些需求,需要使用到地图的周边功能. 完整的项目代码请查看 我的github 一 .先看要实现的结果,参考了链 ...
- vue Baidu Map --- vue百度地图插件
vue Baidu Map 官网:https://dafrok.github.io/vue-baidu-map/#/zh/start/installation javascript 官网:http:/ ...
- VUE 高德地图选取地址组件开发
高德地图文档地址 http://lbs.amap.com/api/lightmap/guide/picker/ 结合步骤: 1.通过iframe内嵌引入高德地图组件 key就选你自己申请的key &l ...
- vue + 百度地图api
主要分解为如下步骤: (1)在html文件中引入百度地图, <script type="text/javascript" src="http://api.map.b ...
- vue 百度地图多标注展示和点击标注进行的提示
index.html中加入script <script type="text/javascript" src="http://api.map.baidu.com/a ...
- vue 高德地图
index.html <link rel="stylesheet" href="http://cache.amap.com/lbs/static/main1119. ...
- Vue 高德地图 路径规划 画点
CDN 方式 <!--引入高德地图JSAPI --> <script src="//webapi.amap.com/maps?v=1.4.13&key=您申请的ke ...
- vue & 百度地图:在地图上绘制多边形
<template> <div class="hello"> <div style="margin-bottom:10px"> ...
- vue & 百度地图:使用百度地图
index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...
- vue百度地图插件
安装 npm i --save vue-baidu-map 代码 <template> <div> <baidu-map v-bind:style="mapSt ...
随机推荐
- nfs 加 auto 自动挂载/etc/fstab;autofs
一,用/etc/fstab 1.在/etc/fstab里面添加一条配置文件 vim /etc/fstab #在里面添加一条配置信息 192.168.200.10:/opt/share2 /mnt/sh ...
- golang 的 net/http 和 net/rpc 的区别, rpc 效率比 http 高?
在Go语言中,net/http 和 net/rpc 是两个不同的包,它们分别用于实现不同的网络通信模式: net/http: net/http 包主要用于构建Web服务和客户端,它实现了HTTP协议, ...
- 瑞数456vmp逆向分析
声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 目标网站 aHR0cHM6 ...
- css做多列瀑布流
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8 ...
- vue侦听器 - watch
使用watch来侦听data中数据的变化,watch中的属性一定是data 中已经存在的数据. 当需要监听一个对象的改变时,普通的watch方法无法监听到对象内部属性的改变,只有data中的数据才能够 ...
- kettle从入门到精通 第五十一课 ETL之kettle Avro input
1.我们在学习nifi的过程中有接触到Avro schema,当时我在想kettle应该也支持Avro,果不其然kettle也是支持Avro文件的读和写的.今天我们一起来学习下kettle中如何使用A ...
- CloseableHttpClient设置超时时间demo 未设置默认是2分钟
# CloseableHttpClient设置超时时间demo 未设置默认是2分钟 import org.apache.http.HttpHeaders; import org.apache.http ...
- 使用python脚本玩转古早TCAD软件(待更新)
前言 TCAD(Technology Computer Aided Design),虽然原名中没有与半导体器件有关的词汇,但这种软件便是半导体工艺模拟及器件模拟的工具,可以说是EDA软件的一种.TCA ...
- Ajax分析与爬取实战
Ajax 分析与爬取实战 准备工作 安装好 Python3 了解 Python HTTP 请求库 requests 的基本用法 了解 Ajax 基础知识和分析 Ajax 的基本方法 爬取目标 以一个示 ...
- Android 自定义带动画的柱状图
功能分析 假设要使用柱状图展示用户一周的数据,通用的做法是对接三方图表SDK或者自己通过代码绘制. 1.三方SDK通常包体较大,且定制性差,对特定的UI需求兼容性差; 2.自己绘制,比较复杂,而且要考 ...