一、文档资料:

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 地图接入的更多相关文章

  1. vue 高德地图之玩转周边

    前言:在之前的博客中,有成功引入高德地图,这是以前的地址  vue 调用高德地图. 因为一些需求,需要使用到地图的周边功能. 完整的项目代码请查看  我的github 一 .先看要实现的结果,参考了链 ...

  2. vue Baidu Map --- vue百度地图插件

    vue Baidu Map 官网:https://dafrok.github.io/vue-baidu-map/#/zh/start/installation javascript 官网:http:/ ...

  3. VUE 高德地图选取地址组件开发

    高德地图文档地址 http://lbs.amap.com/api/lightmap/guide/picker/ 结合步骤: 1.通过iframe内嵌引入高德地图组件 key就选你自己申请的key &l ...

  4. vue + 百度地图api

    主要分解为如下步骤: (1)在html文件中引入百度地图, <script type="text/javascript" src="http://api.map.b ...

  5. vue 百度地图多标注展示和点击标注进行的提示

    index.html中加入script <script type="text/javascript" src="http://api.map.baidu.com/a ...

  6. vue 高德地图

    index.html <link rel="stylesheet" href="http://cache.amap.com/lbs/static/main1119. ...

  7. Vue 高德地图 路径规划 画点

    CDN 方式 <!--引入高德地图JSAPI --> <script src="//webapi.amap.com/maps?v=1.4.13&key=您申请的ke ...

  8. vue & 百度地图:在地图上绘制多边形

    <template> <div class="hello"> <div style="margin-bottom:10px"> ...

  9. vue & 百度地图:使用百度地图

    index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> &l ...

  10. vue百度地图插件

    安装 npm i --save vue-baidu-map 代码 <template> <div> <baidu-map v-bind:style="mapSt ...

随机推荐

  1. 解决 Https 站点请求 Http 接口服务后报 the content must be served over HTTPS 错误的问题

    问题分析 之前将自己所有的 Http 站点全部更新为 Https 站点,但是在请求后台接口服务的时候还是 Http 请求,导致部署之后,直接在控制台报 This request has been bl ...

  2. WIN11 WINDOWS 11 INTEL 12-13代大小核默认调度策略

    WIN11 WINDOWS 11 INTEL 12-13代大小核默认调度策略 生效的异类策略:使用异类策略0. 异类线程调度策略:自动. 异类短运行线程调度策略:自动.

  3. SRE Google 运维解密读书笔记一:SRE 方法论概述

    SRE Google 运维解密,是 SRE 领域的启蒙之作,讲述了 Google 的 SRE 实践,SRE 就是从 Google 流传出来的.本文是读书笔记,第一篇,概述 SRE 方法论.帮大家把书读 ...

  4. 百度面试:如何用Redis实现限流?

    高并发系统有三大特征:限流.缓存和熔断,所以限流已经成为当下系统开发中必备的功能了.那么,什么是限流?如何实现限流?使用 Redis 能不能实现限流?接下来我们一起来看. 1.什么是限流? 限流是指在 ...

  5. work05

    第一题:分析以下需求,并用代码实现 手机类Phone 属性: 品牌brand 价格price 行为: 打电话call() 发短信sendMessage() 玩游戏playGame() 要求: 1.按照 ...

  6. 构建SaaS能力,加速数字化转型!猪齿鱼将在华为云快成长直播间开讲!

    时代的浪潮驱动着企业数字化转型.伴随着新基建.云计算成为国家战略的重要环节之一,"千行百业"开始专注于数字化转型,企业纷纷使用软件提升研发.销售.市场.消费者等不同场景下的效率,S ...

  7. Flash驱动控制--芯片擦除(SPI协议)

    摘要: 本篇博客具体包括SPI协议的基本原理.模式选择以及时序逻辑要求,采用FPGA(EPCE4),通过SPI通信协议,对flash(W25Q16BV)存储的固化程序进行芯片擦除操作. 关键词:SPI ...

  8. linux下后台运行程序

    文章目录 背景 nohup命令 setsid命令 pm2 背景 后台运行程序的时候,如果退出当前的终端(session),你运行的所有程序(包括后台程序),都将被关闭. 原因是:你运行的程序都是你的终 ...

  9. 看李沐的 ViT 串讲

    ViT 概括 论文题目:AN IMAGE IS WORTH 16X16 WORDS: TRANSFORMERS FOR IMAGE RECOGNITION AT SCALE 论文地址:https:// ...

  10. Linux设备模型:2、基本对象 Kobject、Kset、Ktype

    原文:http://www.wowotech.net/device_model/kobject.html 作者:wowo 发布于:2014-3-7 0:25 分类:统一设备模型 前言 Kobject是 ...