:::warning 下钻功能实现逻辑:通过获取当前地区code作为后续筛选区域的父级code,最后一个层级只展示当前code区域(不同层级地图缩放程度不同) :::

一、地图展示效果

mapbox下钻数据展示 - 图1

二、页面地图初始化

  1. // 页面初始化方法
  2. async initMapbox(map) {
  3. // 存储 mapbox 地图实例
  4. this.mapboxInstance = map
  5. this.mapboxInstance.addLayer({
  6. id: 'deep-bg',
  7. type: 'background', // 背景图层
  8. paint: {
  9. 'background-color': 'rgba(1, 18, 48, .48)',
  10. },
  11. })
  12. // 添加地图上点位icon
  13. // await loadImageIcon(this.mapboxInstance)
  14. // 添加数据源
  15. await addSource(this.mapboxInstance, ['zhejiang_region', 'zhejiang_region_point', 'project_planning'])
  16. // 添加3d边缘线
  17. await addLineTranslate(this.mapboxInstance)
  18. // 添加填充色
  19. await addZJFillLayer(this.mapboxInstance)
  20. // 添加边界以及填充图层
  21. await addZjLayer(this.mapboxInstance)
  22. // 添加名字图层
  23. await addAddressNameLayer(this.mapboxInstance)
  24. // 项目类型图层-竹材分解点、初级园区、竹产业园区
  25. // await addServiceLayer(this.mapboxInstance)
  26. // 添加鼠标移入高亮数据
  27. this.layerHover()
  28. // 绑定点图层上图标点击事件
  29. // this.bindClickEvent(this.mapboxInstance)
  30. // 绑定图层点击事件(该事件要在点图层事件之后,否则无法使用冒泡阻止图层事件)
  31. // 该事件为下钻功能
  32. this.layerEnterNext()
  33. setTimeout(() => {
  34. this.mapboxInstance.flyTo({
  35. pitch: 8,
  36. })
  37. }, 1500)
  38. },

三、初始化添加的图层

  1. // addSource
  2. import { geoUrl } from '@/fetch/env'
  3. // 添加浙江省 mapbox source
  4. export default function addSource(map, source = []) {
  5. const sourceList = source.length
  6. ? source
  7. : [
  8. 'zhejiang_region',
  9. 'zhejiang_region_point',
  10. 'bamboo_plant_spot',
  11. 'bamboo_productivity_spot',
  12. 'suitability_evaluation',
  13. 'risk_evaluation',
  14. ]
  15. for (const name of sourceList) {
  16. if (name) {
  17. map.addSource(name, {
  18. type: 'vector',
  19. scheme: 'tms',
  20. tiles: [`${geoUrl}/geoserver/gwc/service/tms/1.0.0/bambooindustry%3A${name}@EPSG%3A900913@pbf/{z}/{x}/{y}.pbf`],
  21. })
  22. }
  23. }
  24. }
  1. // addLineTranslate
  2. export default function addLineTranslate(map, code = '330000') {
  3. // 采用边界偏移的方式,添加杭州区域下沉模糊效果(多个图层,每个图层偏移量递增)
  4. 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]
  5. blurValueList.forEach((item, index) => {
  6. if (map.getLayer(`zj-line-blur-layer${index}`)) {
  7. map.setFilter(`zj-line-blur-layer${index}`, ['match', ['get', 'code'], code, true, false])
  8. return
  9. }
  10. let tempOption = {
  11. id: `zj-line-blur-layer${index}`,
  12. source: `zhejiang_region`,
  13. 'source-layer': `zhejiang_region`,
  14. type: 'line',
  15. layout: {
  16. 'line-cap': 'round',
  17. 'line-join': 'round',
  18. },
  19. filter: ['match', ['get', 'code'], code, true, false],
  20. paint: {
  21. 'line-color': `rgba(129, 252, 254, ${item})`,
  22. 'line-width': 1,
  23. // 横向:左右无偏移,纵向:每一个图层向下偏移+1px
  24. 'line-translate': [0, (index + 1) * 1],
  25. 'line-translate-anchor': 'map',
  26. },
  27. }
  28. map.addLayer(tempOption)
  29. })
  30. }
  1. // addZJFillLayer
  2. export default function addZJFillLayer(map, code = '330000', lastLevel = false) {
  3. return new Promise(async (resolve, reject) => {
  4. try {
  5. if (map.getLayer('zj-fill-layer')) {
  6. // 当前地区code作为后续筛选区域的父级code,最后一个层级只展示当前code区域
  7. map.setFilter('zj-fill-layer', ['match', ['get', lastLevel ? 'code' : 'parent_code'], code, true, false])
  8. } else {
  9. // zj 下的城市填充颜色
  10. await map.addLayer({
  11. id: 'zj-fill-layer',
  12. source: 'zhejiang_region',
  13. 'source-layer': 'zhejiang_region',
  14. type: 'fill',
  15. filter: ['match', ['get', 'parent_code'], code, true, false],
  16. paint: {
  17. 'fill-color': 'rgba(97,178,173,1)',
  18. },
  19. })
  20. }
  21. resolve()
  22. } catch (err) {
  23. reject(err)
  24. }
  25. })
  26. }
  1. // 添加浙江边界图层以及下一级的边界图层
  2. // addZjLayer
  3. export default function addZjLayer(map, code = '330000', lastLevel = false) {
  4. return new Promise(async (resolve, reject) => {
  5. try {
  6. // 添加hover高亮图层
  7. if (!map.getLayer('zj-fill-hover-layer')) {
  8. // zj 下的城市填充颜色 hover
  9. await map.addLayer({
  10. id: 'zj-fill-hover-layer',
  11. source: 'zhejiang_region',
  12. 'source-layer': 'zhejiang_region',
  13. type: 'fill',
  14. filter: ['==', 'name', ''],
  15. paint: {
  16. 'fill-color': 'rgba(21,207,210,1)',
  17. },
  18. })
  19. }
  20. if (map.getLayer('zj-layer')) {
  21. map.setFilter('zj-layer', ['match', ['get', 'code'], code, true, false])
  22. } else {
  23. // 添加 zj 边界
  24. await map.addLayer({
  25. id: 'zj-layer',
  26. source: 'zhejiang_region',
  27. 'source-layer': 'zhejiang_region',
  28. type: 'line',
  29. layout: {
  30. 'line-cap': 'round',
  31. 'line-join': 'round',
  32. },
  33. filter: ['match', ['get', 'code'], code, true, false],
  34. paint: {
  35. 'line-color': 'rgba(129, 252, 254, 1)',
  36. 'line-width': 3,
  37. },
  38. })
  39. }
  40. if (map.getLayer('zj_city-layer')) {
  41. map.setFilter('zj_city-layer', ['match', ['get', 'parent_code'], code, true, false])
  42. } else {
  43. // 添加ZJ下城市边界
  44. await map.addLayer({
  45. id: 'zj_city-layer',
  46. source: 'zhejiang_region',
  47. 'source-layer': 'zhejiang_region',
  48. type: 'line',
  49. layout: {
  50. 'line-cap': 'round',
  51. 'line-join': 'round',
  52. },
  53. filter: ['match', ['get', 'parent_code'], code, true, false],
  54. paint: {
  55. 'line-color': 'rgba(129, 252, 254, 1)',
  56. 'line-width': 1,
  57. },
  58. })
  59. }
  60. resolve()
  61. } catch (err) {
  62. reject(err)
  63. }
  64. })
  65. }
  1. // addAddressNameLayer
  2. export default async function addAddressNameLayer(map, parentCode = '330000', lastLevel = false) {
  3. // 添加镇街道名称图层
  4. if (map.getLayer('address_name-layer')) {
  5. if (lastLevel) {
  6. map.setFilter('address_name-layer', ['match', ['get', 'code'], parentCode, true, false])
  7. } else {
  8. map.setFilter('address_name-layer', ['match', ['get', 'parent_code'], parentCode, true, false])
  9. }
  10. return
  11. }
  12. await map.addLayer({
  13. id: `address_name-layer`,
  14. source: 'zhejiang_region_point',
  15. 'source-layer': 'zhejiang_region_point',
  16. type: 'symbol',
  17. filter: ['match', ['get', 'parent_code'], parentCode, true, false],
  18. layout: {
  19. 'text-field': ['get', 'name'],
  20. 'text-size': 14,
  21. 'icon-allow-overlap': true,
  22. 'icon-ignore-placement': true,
  23. 'text-allow-overlap': true,
  24. },
  25. paint: {
  26. 'text-color': '#061A32',
  27. // 'text-halo-width': 0.1,
  28. // 'text-halo-blur': 0.5,
  29. // 'text-halo-color': 'rgb(6, 26, 50)',
  30. },
  31. })
  32. }

四、鼠标hover区域高亮事件

  1. // layer hover
  2. layerHover() {
  3. // 展示hover layer
  4. this.mapboxInstance.on('mousemove', 'zj-fill-layer', (e) => {
  5. if (e.features.length) {
  6. this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', e.features[0].properties.name])
  7. }
  8. })
  9. // 隐藏hover layer
  10. this.mapboxInstance.on(
  11. 'mouseleave',
  12. 'zj-fill-layer',
  13. debounce(() => {
  14. this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', ''])
  15. }, 50)
  16. )
  17. },
  18. // debounce 防抖函数
  19. export default function debounce(fn, delay = 500) {
  20. let timer = null
  21. return function () {
  22. if (timer) {
  23. clearTimeout(timer)
  24. }
  25. timer = setTimeout(() => {
  26. fn.apply(this, arguments)
  27. timer = null
  28. }, delay)
  29. }
  30. }

五、点击进入地区

  1. // 点击进入地区
  2. layerEnterNext() {
  3. this.mapboxInstance.on('click', 'zj-fill-layer', async (e) => {
  4. // 防止点击图标触发图层事件
  5. if (e.defaultPrevented) {
  6. return
  7. }
  8. e.preventDefault()
  9. // 最后一个层级的地区点击无下钻
  10. if (this.mapRedirect.length === 4) return
  11. // queryRenderedFeatures 返回表示满足查询参数的可见特征的 GeoJSON Feature 对象数组
  12. const features = this.mapboxInstance.queryRenderedFeatures(e.point, { layers: ['zj-fill-layer'] })
  13. console.log(features)
  14. if (features.length) {
  15. try {
  16. const { code, latitude, longitude, level, name } = features[0].properties
  17. console.log('features[0].properties: ', features[0].properties)
  18. // 保存路径
  19. this.mapRedirect.push({
  20. name,
  21. code,
  22. longitude,
  23. latitude,
  24. level,
  25. })
  26. this.updateLayerByCode(code, level, latitude, longitude)
  27. } catch (e) {
  28. console.log(e)
  29. }
  30. }
  31. })
  32. },
  33. // 根据code进入下一级地区
  34. async updateLayerByCode(code, level, latitude, longitude) {
  35. // 清空高亮图层
  36. this.mapboxInstance.setFilter('zj-fill-hover-layer', ['==', 'name', ''])
  37. // 通过code过滤图层
  38. await addLineTranslate(this.mapboxInstance, code)
  39. await addZJFillLayer(this.mapboxInstance, code, level == 4)
  40. await addZjLayer(this.mapboxInstance, code, level == 4)
  41. await addAddressNameLayer(this.mapboxInstance, code, level == 4)
  42. // await addServiceLayer(this.mapboxInstance, code, this.typeList, level)
  43. this.mapboxInstance.flyTo({
  44. // 由于初期数据中心点准确度较低,稍微做了一下优化
  45. // 模型完善后可不需处理
  46. center: [longitude - this.centerDevnation[level][0], latitude - this.centerDevnation[level][1]],
  47. zoom: this.zoomList[level],
  48. })
  49. },

六、应用

  1. // html部分
  2. <MapWrapper :initMapbox="initMapbox"></MapWrapper>
  3. // data 数据源部分
  4. data() {
  5. return {
  6. mapboxInstance: null,
  7. // zoom 层级map
  8. zoomList: {
  9. 1: 6.5,
  10. 2: 8,
  11. 3: 10,
  12. 4: 11,
  13. },
  14. // 中心点位偏移量;开发阶段配置
  15. centerDevnation: {
  16. 1: [0, 0],
  17. 2: [0, 0.4],
  18. 3: [0, 0.1],
  19. 4: [0, 0],
  20. },
  21. // 路径导航数组
  22. mapRedirect: [
  23. {
  24. name: '浙江省',
  25. code: '330000',
  26. longitude: 120.5,
  27. latitude: 28,
  28. level: 1,
  29. },
  30. ],
  31. }
  32. },

七、左上角位置信息展示组件

  1. // AddressRedirect 组件
  2. <template>
  3. <div class="address-redirect">
  4. <img src="@/assets/images/common/point.png" alt="" v-if="paths.length === 1" />
  5. <img class="back" src="@/assets/images/common/back.png" v-else @click="back" />
  6. <div class="link" v-for="(path, index) in paths" :key="index" :class="{ last: index === paths.length - 1 }">
  7. <div class="name" @click="changePath(index)">{{ path.name }}</div>
  8. <div class="line" v-if="index !== paths.length - 1"></div>
  9. <div class="split" v-if="index !== paths.length - 1">&nbsp;/&nbsp;</div>
  10. </div>
  11. </div>
  12. </template>
  13. <script>
  14. /* 地图四级地址导航组件 */
  15. export default {
  16. name: 'AddressRedirect',
  17. props: {
  18. paths: {
  19. type: Array,
  20. required: true,
  21. },
  22. },
  23. data() {
  24. return {}
  25. },
  26. methods: {
  27. changePath(index) {
  28. if (index === this.paths.length - 1) return
  29. this.$emit('changeRedirect', this.paths.slice(0, index + 1))
  30. },
  31. back() {
  32. this.$emit('changeRedirect', this.paths.slice(0, this.paths.length - 1))
  33. },
  34. },
  35. }
  36. </script>
  37. <style lang="scss" scoped>
  38. .address-redirect {
  39. color: green;
  40. pointer-events: auto;
  41. display: flex;
  42. img {
  43. width: 20px;
  44. height: 20px;
  45. margin-right: 3px;
  46. &.back {
  47. cursor: pointer;
  48. }
  49. }
  50. .link {
  51. display: flex;
  52. font-size: 14px;
  53. font-family: PingFangSC-Medium, PingFang SC;
  54. font-weight: 500;
  55. color: #81fcfe;
  56. line-height: 20px;
  57. position: relative;
  58. cursor: pointer;
  59. .line {
  60. position: absolute;
  61. top: 16px;
  62. width: calc(100% - 16px);
  63. height: 2px;
  64. background: #81fcfe;
  65. }
  66. &.last {
  67. font-size: 14px;
  68. color: #81fcfe;
  69. font-family: PingFangSC-Regular, PingFang SC;
  70. font-weight: 400;
  71. cursor: not-allowed;
  72. }
  73. }
  74. }
  75. </style>
  1. // AddressRedirect 父组件事件
  2. // Html部分
  3. <AddressRedirect :paths="mapRedirect" @changeRedirect="changeRedirect"></AddressRedirect>
  4. // 事件
  5. // 修改当前选择的地区
  6. changeRedirect(paths) {
  7. this.mapRedirect = paths
  8. const { code, level, latitude, longitude } = paths[paths.length - 1]
  9. this.updateLayerByCode(code, level, latitude, longitude)
  10. },

ps.关于mapbox筛选

  1. // A & B & C
  2. filter: ['match', ['get', 'qstatus'], 2, ['match', ['get', 'taski_id'], taskiId, true, false], false]
  3. filter: [
  4. 'all',
  5. ['match', ['get', 'code'], A, ['match', ['get', 'taski_id'], B, ['match', ['get', 'id'], C, true, false], false], false],
  6. ],
  7. // A || B || C
  8. filter: [
  9. 'all',
  10. ['any', [
  11. ['match', ['get', 'type'], A, true, false],
  12. ['match', ['get', 'type'], B, true, false],
  13. ['match', ['get', 'type'], C, true, false]
  14. ]
  15. ],
  16. ],