:::warning 下钻功能实现逻辑:通过获取当前地区code作为后续筛选区域的父级code,最后一个层级只展示当前code区域(不同层级地图缩放程度不同) :::
一、地图展示效果
二、页面地图初始化
// 页面初始化方法
async initMapbox(map) {
// 存储 mapbox 地图实例
this.mapboxInstance = map
this.mapboxInstance.addLayer({
id: 'deep-bg',
type: 'background', // 背景图层
paint: {
'background-color': 'rgba(1, 18, 48, .48)',
},
})
// 添加地图上点位icon
// await loadImageIcon(this.mapboxInstance)
// 添加数据源
await addSource(this.mapboxInstance, ['zhejiang_region', 'zhejiang_region_point', 'project_planning'])
// 添加3d边缘线
await addLineTranslate(this.mapboxInstance)
// 添加填充色
await addZJFillLayer(this.mapboxInstance)
// 添加边界以及填充图层
await addZjLayer(this.mapboxInstance)
// 添加名字图层
await addAddressNameLayer(this.mapboxInstance)
// 项目类型图层-竹材分解点、初级园区、竹产业园区
// await addServiceLayer(this.mapboxInstance)
// 添加鼠标移入高亮数据
this.layerHover()
// 绑定点图层上图标点击事件
// this.bindClickEvent(this.mapboxInstance)
// 绑定图层点击事件(该事件要在点图层事件之后,否则无法使用冒泡阻止图层事件)
// 该事件为下钻功能
this.layerEnterNext()
setTimeout(() => {
this.mapboxInstance.flyTo({
pitch: 8,
})
}, 1500)
},
三、初始化添加的图层
// addSource
import { geoUrl } from '@/fetch/env'
// 添加浙江省 mapbox source
export default function addSource(map, source = []) {
const sourceList = source.length
? source
: [
'zhejiang_region',
'zhejiang_region_point',
'bamboo_plant_spot',
'bamboo_productivity_spot',
'suitability_evaluation',
'risk_evaluation',
]
for (const name of sourceList) {
if (name) {
map.addSource(name, {
type: 'vector',
scheme: 'tms',
tiles: [`${geoUrl}/geoserver/gwc/service/tms/1.0.0/bambooindustry%3A${name}@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf`],
})
}
}
}
// addLineTranslate
export default function addLineTranslate(map, code = '330000') {
// 采用边界偏移的方式,添加杭州区域下沉模糊效果(多个图层,每个图层偏移量递增)
const blurValueList = [0.6, 0.5, 0.5, 0.4, 0.4, 0.3, 0.3, 0.2, 0.2, 0.1, 0.1, 0.1, 0.3, 0.3]
blurValueList.forEach((item, index) => {
if (map.getLayer(`zj-line-blur-layer${index}`)) {
map.setFilter(`zj-line-blur-layer${index}`, ['match', ['get', 'code'], code, true, false])
return
}
let tempOption = {
id: `zj-line-blur-layer${index}`,
source: `zhejiang_region`,
'source-layer': `zhejiang_region`,
type: 'line',
layout: {
'line-cap': 'round',
'line-join': 'round',
},
filter: ['match', ['get', 'code'], code, true, false],
paint: {
'line-color': `rgba(129, 252, 254, ${item})`,
'line-width': 1,
// 横向:左右无偏移,纵向:每一个图层向下偏移+1px
'line-translate': [0, (index + 1) * 1],
'line-translate-anchor': 'map',
},
}
map.addLayer(tempOption)
})
}
// addZJFillLayer
export default function addZJFillLayer(map, code = '330000', lastLevel = false) {
return new Promise(async (resolve, reject) => {
try {
if (map.getLayer('zj-fill-layer')) {
// 当前地区code作为后续筛选区域的父级code,最后一个层级只展示当前code区域
map.setFilter('zj-fill-layer', ['match', ['get', lastLevel ? 'code' : 'parent_code'], code, true, false])
} else {
// zj 下的城市填充颜色
await map.addLayer({
id: 'zj-fill-layer',
source: 'zhejiang_region',
'source-layer': 'zhejiang_region',
type: 'fill',
filter: ['match', ['get', 'parent_code'], code, true, false],
paint: {
'fill-color': 'rgba(97,178,173,1)',
},
})
}
resolve()
} catch (err) {
reject(err)
}
})
}
// 添加浙江边界图层以及下一级的边界图层
// addZjLayer
export default function addZjLayer(map, code = '330000', lastLevel = false) {
return new Promise(async (resolve, reject) => {
try {
// 添加hover高亮图层
if (!map.getLayer('zj-fill-hover-layer')) {
// zj 下的城市填充颜色 hover
await map.addLayer({
id: 'zj-fill-hover-layer',
source: 'zhejiang_region',
'source-layer': 'zhejiang_region',
type: 'fill',
filter: ['==', 'name', ''],
paint: {
'fill-color': 'rgba(21,207,210,1)',
},
})
}
if (map.getLayer('zj-layer')) {
map.setFilter('zj-layer', ['match', ['get', 'code'], code, true, false])
} else {
// 添加 zj 边界
await map.addLayer({
id: 'zj-layer',
source: 'zhejiang_region',
'source-layer': 'zhejiang_region',
type: 'line',
layout: {
'line-cap': 'round',
'line-join': 'round',
},
filter: ['match', ['get', 'code'], code, true, false],
paint: {
'line-color': 'rgba(129, 252, 254, 1)',
'line-width': 3,
},
})
}
if (map.getLayer('zj_city-layer')) {
map.setFilter('zj_city-layer', ['match', ['get', 'parent_code'], code, true, false])
} else {
// 添加ZJ下城市边界
await map.addLayer({
id: 'zj_city-layer',
source: 'zhejiang_region',
'source-layer': 'zhejiang_region',
type: 'line',
layout: {
'line-cap': 'round',
'line-join': 'round',
},
filter: ['match', ['get', 'parent_code'], code, true, false],
paint: {
'line-color': 'rgba(129, 252, 254, 1)',
'line-width': 1,
},
})
}
resolve()
} catch (err) {
reject(err)
}
})
}
// addAddressNameLayer
export default async function addAddressNameLayer(map, parentCode = '330000', lastLevel = false) {
// 添加镇街道名称图层
if (map.getLayer('address_name-layer')) {
if (lastLevel) {
map.setFilter('address_name-layer', ['match', ['get', 'code'], parentCode, true, false])
} else {
map.setFilter('address_name-layer', ['match', ['get', 'parent_code'], parentCode, true, false])
}
return
}
await map.addLayer({
id: `address_name-layer`,
source: 'zhejiang_region_point',
'source-layer': 'zhejiang_region_point',
type: 'symbol',
filter: ['match', ['get', 'parent_code'], parentCode, true, false],
layout: {
'text-field': ['get', 'name'],
'text-size': 14,
'icon-allow-overlap': true,
'icon-ignore-placement': true,
'text-allow-overlap': true,
},
paint: {
'text-color': '#061A32',
// 'text-halo-width': 0.1,
// 'text-halo-blur': 0.5,
// 'text-halo-color': 'rgb(6, 26, 50)',
},
})
}
四、鼠标hover区域高亮事件
// layer hover
layerHover() {
// 展示hover layer
this.mapboxInstance.on('mousemove', 'zj-fill-layer', (e) => {
if (e.features.length) {
this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', e.features[0].properties.name])
}
})
// 隐藏hover layer
this.mapboxInstance.on(
'mouseleave',
'zj-fill-layer',
debounce(() => {
this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', ''])
}, 50)
)
},
// debounce 防抖函数
export default function debounce(fn, delay = 500) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, arguments)
timer = null
}, delay)
}
}
五、点击进入地区
// 点击进入地区
layerEnterNext() {
this.mapboxInstance.on('click', 'zj-fill-layer', async (e) => {
// 防止点击图标触发图层事件
if (e.defaultPrevented) {
return
}
e.preventDefault()
// 最后一个层级的地区点击无下钻
if (this.mapRedirect.length === 4) return
// queryRenderedFeatures 返回表示满足查询参数的可见特征的 GeoJSON Feature 对象数组
const features = this.mapboxInstance.queryRenderedFeatures(e.point, { layers: ['zj-fill-layer'] })
console.log(features)
if (features.length) {
try {
const { code, latitude, longitude, level, name } = features[0].properties
console.log('features[0].properties: ', features[0].properties)
// 保存路径
this.mapRedirect.push({
name,
code,
longitude,
latitude,
level,
})
this.updateLayerByCode(code, level, latitude, longitude)
} catch (e) {
console.log(e)
}
}
})
},
// 根据code进入下一级地区
async updateLayerByCode(code, level, latitude, longitude) {
// 清空高亮图层
this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', ''])
// 通过code过滤图层
await addLineTranslate(this.mapboxInstance, code)
await addZJFillLayer(this.mapboxInstance, code, level == 4)
await addZjLayer(this.mapboxInstance, code, level == 4)
await addAddressNameLayer(this.mapboxInstance, code, level == 4)
// await addServiceLayer(this.mapboxInstance, code, this.typeList, level)
this.mapboxInstance.flyTo({
// 由于初期数据中心点准确度较低,稍微做了一下优化
// 模型完善后可不需处理
center: [longitude - this.centerDevnation[level][0], latitude - this.centerDevnation[level][1]],
zoom: this.zoomList[level],
})
},
六、应用
// html部分
<MapWrapper :initMapbox="initMapbox"></MapWrapper>
// data 数据源部分
data() {
return {
mapboxInstance: null,
// zoom 层级map
zoomList: {
1: 6.5,
2: 8,
3: 10,
4: 11,
},
// 中心点位偏移量;开发阶段配置
centerDevnation: {
1: [0, 0],
2: [0, 0.4],
3: [0, 0.1],
4: [0, 0],
},
// 路径导航数组
mapRedirect: [
{
name: '浙江省',
code: '330000',
longitude: 120.5,
latitude: 28,
level: 1,
},
],
}
},
七、左上角位置信息展示组件
// AddressRedirect 组件
<template>
<div class="address-redirect">
<img src="@/assets/images/common/point.png" alt="" v-if="paths.length === 1" />
<img class="back" src="@/assets/images/common/back.png" v-else @click="back" />
<div class="link" v-for="(path, index) in paths" :key="index" :class="{ last: index === paths.length - 1 }">
<div class="name" @click="changePath(index)">{{ path.name }}</div>
<div class="line" v-if="index !== paths.length - 1"></div>
<div class="split" v-if="index !== paths.length - 1"> / </div>
</div>
</div>
</template>
<script>
/* 地图四级地址导航组件 */
export default {
name: 'AddressRedirect',
props: {
paths: {
type: Array,
required: true,
},
},
data() {
return {}
},
methods: {
changePath(index) {
if (index === this.paths.length - 1) return
this.$emit('changeRedirect', this.paths.slice(0, index + 1))
},
back() {
this.$emit('changeRedirect', this.paths.slice(0, this.paths.length - 1))
},
},
}
</script>
<style lang="scss" scoped>
.address-redirect {
color: green;
pointer-events: auto;
display: flex;
img {
width: 20px;
height: 20px;
margin-right: 3px;
&.back {
cursor: pointer;
}
}
.link {
display: flex;
font-size: 14px;
font-family: PingFangSC-Medium, PingFang SC;
font-weight: 500;
color: #81fcfe;
line-height: 20px;
position: relative;
cursor: pointer;
.line {
position: absolute;
top: 16px;
width: calc(100% - 16px);
height: 2px;
background: #81fcfe;
}
&.last {
font-size: 14px;
color: #81fcfe;
font-family: PingFangSC-Regular, PingFang SC;
font-weight: 400;
cursor: not-allowed;
}
}
}
</style>
// AddressRedirect 父组件事件
// Html部分
<AddressRedirect :paths="mapRedirect" @changeRedirect="changeRedirect"></AddressRedirect>
// 事件
// 修改当前选择的地区
changeRedirect(paths) {
this.mapRedirect = paths
const { code, level, latitude, longitude } = paths[paths.length - 1]
this.updateLayerByCode(code, level, latitude, longitude)
},
ps.关于mapbox筛选
// A & B & C
filter: ['match', ['get', 'qstatus'], 2, ['match', ['get', 'taski_id'], taskiId, true, false], false]
filter: [
'all',
['match', ['get', 'code'], A, ['match', ['get', 'taski_id'], B, ['match', ['get', 'id'], C, true, false], false], false],
],
// A || B || C
filter: [
'all',
['any', [
['match', ['get', 'type'], A, true, false],
['match', ['get', 'type'], B, true, false],
['match', ['get', 'type'], C, true, false]
]
],
],