Loading [MathJax]/jax/output/CommonHTML/config.js
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >mapboxGL中山体背景+边界阴影的一种实现方案

mapboxGL中山体背景+边界阴影的一种实现方案

作者头像
牛老师讲GIS
发布于 2024-05-27 00:24:42
发布于 2024-05-27 00:24:42
34200
代码可运行
举报
运行总次数:0
代码可运行

概述

很多地图可视化的项目中有要求实现如下的效果,本文借助QGISPSturf.js,在mapboxGL中实现山体背景+边界阴影的效果。

实现效果

实现

1. 需要数据

要实现这样的效果,我们需要如下数据:

  1. 山体背景图
  2. 地级市数据
  3. 省级边界数据,可通过地级市数据融合得到
  4. 边界阴影,通过省级边界数据计算获取

测试数据下载地址:https://gitee.com/lzugis15/blogs-demo/blob/master/gansu.zip

2. 数据处理

2.1 省级边界数据

如果没有改数据,可复制一份地级市的数据,在QGIS中开启图层编辑,全选要素,通过Merge选中要素生成。

2.2 山体背景图
1)导出影像

QGIS中添加高德影像图,并添加省边界数据,设置省边界不显示,导出地图。 [图片上传中…(image.png-6c9beb-1716705916905-0)] 根据省边界数据计算导出范围,并设置导出格式为*.tif

2)裁剪影像

将导出的*.tif添加到QGIS中,在Raster菜单下选择栅格裁剪工具,将导出的数据根据省边界数据进行裁剪。

3)导出背景图

跟操作**1)**一样,导出裁剪后的地图,导出格式选择*.png,导出后的图片如下图。

4)处理背景图

导出后的背景图是彩色的,还需要在PS中进一步处理成为蓝色调(可根据需求进行处理)。处理方式是在上面叠加一个图层,设置填充颜色,并设置模式为色相,再将两个图层合并成一个图层,处理后如下图。

2.3 边界阴影

边界阴影效果是将生边界数据进行一定的偏移,这个实现是在代码中实现的,实现代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const center = this.map.getCenter().toArray();
 // 获取地图中心点屏幕位置
const { x, y } = this.map.project(center);
const offset = [6, 6];
// 计算当前级别下横向、纵向偏移的经纬度
const centerOffset = this.map.unproject([x - offset[0], y - offset[1]]).toArray();
const xOffset = centerOffset[0] - center[0],
  yOffset = center[1] - centerOffset[1];
// 深拷贝,防止数据被篡改
const _res = JSON.parse(JSON.stringify(result));
const geometry = _res.geometry;
if (geometry.type === "polygon") {
  geometry.coordinates.forEach((c) => {
    c.forEach((p) => {
      p[0] += xOffset;
      p[1] += yOffset;
    });
  });
} else {
  geometry.coordinates.forEach((c) => {
    c.forEach((p) => {
      p.forEach((_p) => {
        _p[0] += xOffset;
        _p[1] += yOffset;
      });
    });
  });
}

3. 完整实现

完整实现代码如下。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<template>
  <div class="map">
    <my-map
      :onLoad="mapLoaded"
      :style="style"
      :center="[104.29901000000001, 37.94116735562514]"
      :zoom="zoom"
    >
    </my-map>
  </div>
</template>

<script>
import { MyMap } from "@/components/map/index.vue";
import * as turf from "@turf/turf";

class Geojson {
  constructor(features = []) {
    this.features = features;
    this.type = "FeatureCollection";
  }
}

export default {
  components: {
    MyMap,
  },
  data() {
    return {
      map: null,
      zoom: 3.51,
      style: {
        version: 8,
        name: "my-map-style",
        sprite: window.location.href + "icons/sprite",
        glyphs: window.location.href + "fonts/{fontstack}/{range}.pbf",
        sources: {
          "image-admin": {
            url: "/imgs/gansu-bg.png",
            type: "image",
            // 省边界数据的四至
            coordinates: [
              [92.3390100000000018, 42.795259999999999],
              [108.712530000000001, 42.795259999999999],
              [108.712530000000001, 32.5938900000000018],
              [92.3390100000000018, 32.5938900000000018],
            ],
          },
          "admin-boundry": {
            type: "geojson",
            data: new Geojson(),
          },
          "admin-shadow": {
            type: "geojson",
            data: new Geojson(),
          },
          "admin-children-boundry": {
            type: "geojson",
            data: new Geojson(),
          },
          "admin-children-boundry-h": {
            type: "geojson",
            data: new Geojson(),
          },
        },
        layers: [
          {
            id: "admin-shadow-fill",
            source: "admin-shadow",
            type: "fill",
            paint: {
              "fill-color": "#356caa",
              "fill-opacity": 1,
            },
          },
          {
            id: "image-admin",
            source: "image-admin",
            type: "raster",
            paint: {
              "raster-opacity": 0.55,
              "raster-fade-duration": 0,
            },
          },
          {
            id: "admin-children-boundry-fill",
            source: "admin-children-boundry",
            type: "fill",
            paint: {
              "fill-color": "#599AFF",
              "fill-opacity": 0.1,
            },
          },
          {
            id: "admin-children-boundry-fill-h",
            source: "admin-children-boundry-h",
            type: "fill",
            paint: {
              "fill-color": "#599AFF",
              "fill-opacity": 0.5,
            },
          },
          {
            id: "admin-children-boundry-line",
            source: "admin-children-boundry",
            type: "line",
            paint: {
              "line-color": "#bbe6ff",
              "line-width": 1.5,
            },
          },
          {
            id: "admin-children-boundry-line-h",
            source: "admin-children-boundry-h",
            type: "line",
            paint: {
              "line-color": "#bbe6ff",
              "line-width": 2,
            },
          },
          {
            id: "admin-boundry-line",
            source: "admin-boundry",
            type: "line",
            paint: {
              "line-color": "#bbe8ff",
              "line-width": 3,
            },
          },
          {
            id: "admin-children-boundry-label",
            source: "admin-children-boundry",
            type: "symbol",
            layout: {
              'text-allow-overlap': false,
              'text-size': 14,
              'text-rotate': 0,
              'text-field': `{name}`,
            },
            paint: {
              'text-opacity': 1,
              'text-color': '#ffffff',
              'text-halo-blur': 0.1,
              'text-halo-width': 0.1,
              'text-halo-color': '#356caa',
            },
          },
        ],
      },
      adminFeatures: [],
    };
  },
  methods: {
    setBoundry(features) {
      features = JSON.parse(JSON.stringify(features));
      let result = features.splice(0, 1)[0],
        feat2 = features.splice(0, 1)[0];
      while (features.length > 0) {
        result = turf.union(result, feat2);
        feat2 = features.splice(0, 1)[0];
      }
      this.map.getSource("admin-boundry").setData(result);
      setTimeout(() => {
        const center = this.map.getCenter().toArray();
        const { x, y } = this.map.project(center);
        const offset = [6, 6];
        const centerOffset = this.map
          .unproject([x - offset[0], y - offset[1]])
          .toArray();
        const xOffset = centerOffset[0] - center[0],
          yOffset = center[1] - centerOffset[1];
        const _res = JSON.parse(JSON.stringify(result));
        const geometry = _res.geometry;
        if (geometry.type === "polygon") {
          geometry.coordinates.forEach((c) => {
            c.forEach((p) => {
              p[0] += xOffset;
              p[1] += yOffset;
            });
          });
        } else {
          geometry.coordinates.forEach((c) => {
            c.forEach((p) => {
              p.forEach((_p) => {
                _p[0] += xOffset;
                _p[1] += yOffset;
              });
            });
          });
        }
        this.map.getSource("admin-shadow").setData(_res);
      }, 200);
    },

    registerEvent() {
      this.map.on("mousemove", "admin-children-boundry-fill", (e) => {
        const adcode = e.features[0].properties.adcode;
        const feature = this.adminFeatures.find(
          (d) => d.properties.adcode === adcode
        );
        this.map.getSource("admin-children-boundry-h").setData(feature);
        this.map.getCanvasContainer().style.cursor = "pointer";
      });
      this.map.on("mouseout", "admin-children-boundry-fill", (e) => {
        this.map.getSource("admin-children-boundry-h").setData(new Geojson());
        this.map.getCanvasContainer().style.cursor = "default";
      });
    },
    initData() {
      this.map.scrollZoom.disable();
      this.map.doubleClickZoom.disable();
      this.map.dragPan.disable();
      this.map.dragRotate.disable();

      fetch(`/gansu-b.geojson`)
        .then((res) => res.json())
        .then((geojson) => {
          this.setBoundry(geojson.features);
          this.fit2Geojson(geojson);
        });
      fetch(`/gansu-c.geojson`)
        .then((res) => res.json())
        .then((geojson) => {
          this.adminFeatures = geojson.features;
          this.map.getSource("admin-children-boundry").setData(geojson);
          this.registerEvent()
        });
    },
    mapLoaded(map) {
      this.map = map;
      this.initData();
    },
    fit2Geojson(geojson) {
      const [xmin, ymin, xmax, ymax] = turf.bbox(geojson);
      const bbox = [
        [xmin, ymin],
        [xmax, ymax],
      ];
      const padding = 100;
      const options = {
        padding: {
          top: padding,
          bottom: padding,
          left: padding,
          right: padding,
        },
        duration: 100,
      };
      this.map.fitBounds(bbox, options);
    },
  },
};
</script>

<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
</style>
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-05-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
一种基于高德Web API实现沿路画面的实现
牛老师讲GIS
2024/12/30
2030
一种基于高德Web API实现沿路画面的实现
mapboxGL测量实现
讲真,MapboxGL里面虽然有测量的功能,但是不太好用,于是就萌生了自己实现的方法。本文几个turf.js来说说mapboxGL中测量的实现。
牛老师讲GIS
2020/04/22
1.1K0
mapboxGL测量实现
记mapboxGL实现鼠标经过高亮时的一个问题
mapboxGL实现鼠标经过高亮可通过注册图层的mousemove和moveout事件来实现,在mousemove事件中可以拿到当前经过的要素,但是当使用该要素时,发现在某个地图级别下会有线和面数据展示不全的情况。究其原因,发现是mapboxGL在绘图的时候为提升效率也会进行切片,所以图层事件返回的要素时切片后的,当数据范围比较大、地图级别比较大的时候,必然会分成多块。
牛老师讲GIS
2024/12/30
2090
记mapboxGL实现鼠标经过高亮时的一个问题
leaflet和mapboxGL中网格聚类的实现
前面的文章openlayers中网格聚类的实现发出来后,有好多童鞋问到了其他框架的实现,本文就大家看看在leaflet和mapboxGL中如何实现。
牛老师讲GIS
2023/06/10
6050
leaflet和mapboxGL中网格聚类的实现
mapboxGL中的航线动画
牛老师讲GIS
2024/05/24
3150
mapboxGL中的航线动画
使用高德API和MapboxGL实现路径规划并语音播报
本文使用高德API实现位置查询和路径规划,使用MapboxGL完成地图交互与界面展示,并使用Web Speech API实现行驶中路线的实时语音播报。
牛老师讲GIS
2024/11/11
3040
使用高德API和MapboxGL实现路径规划并语音播报
mapboxGL实现室内地图
精确的数需要通过CAD转换,本文为简单演示,是通过qgis中绘制的,数据主要包括如下字段:
牛老师讲GIS
2023/03/24
1.7K0
mapboxGL实现室内地图
仿高德地图实现输入起点和终点规划路径并可切换
本文结合高德API和MapboxGL,仿照手机版高德地图实现用户输入起点和终点位置并模糊搜索选择具体位置,根据选择的起始点位置规划路径,并实现多条路径的切换展示。
牛老师讲GIS
2025/05/31
1550
仿高德地图实现输入起点和终点规划路径并可切换
mapboxGL轨迹展示与播放
历史轨迹回放是GIS很常见的一个功能,本文结合turf.js实现轨迹的展示与播放动画。
牛老师讲GIS
2021/12/21
1.5K1
mapboxGL轨迹展示与播放
二维地图中立体阴影效果实现
前两天有个学员在群里发出来一张截图,效果是一个区域掩膜+边框立体阴影效果,咨询我怎么实现,我看了下心里大概有了一个想法,只是前两天比较忙就没实现,趁着周末就想着验证实现一下。鉴于学员的要求,本文使用的是leaflet框架。
牛老师讲GIS
2023/07/11
7490
二维地图中立体阴影效果实现
mapboxGL中楼层与室内地图的结合展示
质量不够,数量来凑,没错,本文就是来凑数的。前面的几篇文章实现了楼栋与楼层单体化的展示、室内地图的展示,本文结合前面的几篇文章,做一个综合的展示效果。
牛老师讲GIS
2023/07/31
4960
mapboxGL中楼层与室内地图的结合展示
mapboxGL中室内地图的实现
室内地图的实现最大的难点在于数据的收集,常见的方式有:1.基于施工CAD图纸转换;2.基于商场室内导视图进行绘制。本文的数据是截图高德地图SDK室内地图,并在QGIS中叠加高德地图进行配准后进行,对配准后的图像进行数字化而得到的。获取到数据后将数据叠加到mapboxGL中进行展示,并根据数据添加了楼层控制控件。
牛老师讲GIS
2024/12/30
3050
mapboxGL中室内地图的实现
mapboxGL和高德API结合实现路径规划
高德地图路径规划API说明如上图,有行走、公交、驾车等多种路径,本文以行走为例来说明。
牛老师讲GIS
2020/04/08
2K0
mapboxGL中楼层的的展示与单体化
前面有文章说到了室内地图的展示,在本文讲述如何在mapboxGL中如何实现楼层的展示与单体化选中效果。
牛老师讲GIS
2023/07/25
4681
mapboxGL中楼层的的展示与单体化
基于Martin的全国基础底图实现
前面有文章基于Martin实现MapboxGL自定义底图分享了Martin的使用,本文使用网络收集的数据实现了全国基础数据的收集和基础底图。
牛老师讲GIS
2025/05/31
850
基于Martin的全国基础底图实现
GPX数据在mapboxGL中轨迹动画
喜欢跑步的人都会选择一款APP来自己跑步的,常用的有keep、悦跑圈、华为健康等等,每次跑完步,会根据跑步的轨迹绘制轨迹动画。今天咱们讲讲技术,不扯淡,讲一下在mapboxGL中如何实现类似的效果。
牛老师讲GIS
2020/06/08
2.2K0
GPX数据在mapboxGL中轨迹动画
websocket实现GPS数据的实时推送与地图的展示(优化)
前两天,发布了一片文章websocket实现GPS数据的实时推送与地图的展示,文章发出后引来了不少读者的关注,也有不少读者要求做进步一优化。本文应大家的要求,对上文的内容做一个优化,优化地方包括:
牛老师讲GIS
2022/04/28
8490
websocket实现GPS数据的实时推送与地图的展示(优化)
百度地图城市点位数据下载并转换
在浏览百度地图开放平台的时候,发现有个资源下载页面,里面有个城市中心点位和百度地图行政区划adcode映射表数据,这是一个经常使用到的数据,本文实现将这个数据转换为geojson,并借助QGIS转换为经纬度坐标或火星坐标。
牛老师讲GIS
2024/05/24
3550
百度地图城市点位数据下载并转换
mapbox GL台风路径的播放实现
前面的文章中写了基于openlayers4的台风路径播放,最近用到mapbox GL,也要实现相似的功能,网上找了好久都没有找到,于是就放弃了“拿来主义”的想法,只能自己动手了。经过一下午的努力,终于有了一个雏形,在此分享出来,希望对你有用!
牛老师讲GIS
2020/03/23
1.8K0
mapbox GL台风路径的播放实现
前端解析csv或geojson文件并展示
本位通过FileReader实现csv或geojson文件的前端解析并在地图上展示。
牛老师讲GIS
2023/07/11
8030
前端解析csv或geojson文件并展示
相关推荐
一种基于高德Web API实现沿路画面的实现
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验