:::warning 下钻功能实现逻辑:通过获取当前地区code作为后续筛选区域的父级code,最后一个层级只展示当前code区域(不同层级地图缩放程度不同) :::
一、地图展示效果
二、页面地图初始化
// 页面初始化方法async initMapbox(map) {// 存储 mapbox 地图实例this.mapboxInstance = mapthis.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)},
三、初始化添加的图层
// addSourceimport { geoUrl } from '@/fetch/env'// 添加浙江省 mapbox sourceexport 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`],})}}}
// addLineTranslateexport 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)})}
// addZJFillLayerexport 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)}})}
// 添加浙江边界图层以及下一级的边界图层// addZjLayerexport default function addZjLayer(map, code = '330000', lastLevel = false) {return new Promise(async (resolve, reject) => {try {// 添加hover高亮图层if (!map.getLayer('zj-fill-hover-layer')) {// zj 下的城市填充颜色 hoverawait 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)}})}
// addAddressNameLayerexport 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 hoverlayerHover() {// 展示hover layerthis.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 layerthis.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 = nullreturn 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].propertiesconsole.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 层级mapzoomList: {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) returnthis.$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 = pathsconst { code, level, latitude, longitude } = paths[paths.length - 1]this.updateLayerByCode(code, level, latitude, longitude)},
ps.关于mapbox筛选
// A & B & Cfilter: ['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 || Cfilter: ['all',['any', [['match', ['get', 'type'], A, true, false],['match', ['get', 'type'], B, true, false],['match', ['get', 'type'], C, true, false]]],],
