Vue3 + Mapbox 加载 SHP 转换的矢量瓦片 (Vector Tiles)

在 WebGIS 开发中,我们经常遇到甲方甩过来一个几百兆的 .shp (Shapefile) 文件,要求在地图上展示。

如果直接在前端用 file-loader 解析 SHP,浏览器内存绝对原地爆炸。最优雅的解决方案是将 SHP 转换为矢量瓦片 (Vector Tiles),按需加载。

今天这篇博客就教大家:如何利用 Mapbox Studio 将 SHP 转为瓦片,并在 Vue3项目中加载并实现交互。

第一步:数据处理 (SHP 上云)

前端加载矢量瓦片,首先得有瓦片服务。Mapbox 提供了非常好用的自带托管服务。

  1. 准备数据 :将你的 .shp 文件以及同名的 .dbf, .prj, .shx 文件一起压缩成一个 .zip 包。

  2. 上传 Mapbox Studio

    • 登录 Mapbox Studio

    • 点击右上角 Tilesets -> New tileset

    • 上传你的 ZIP 包。

  3. 获取关键信息 (非常重要!): 上传成功后,点进这个 Tileset,你通过右侧面板获取两个核心参数,代码里要用:

    • Tileset ID (例如: gisdog.t63e74ab) -> 对应代码中的 url

    • Layer Name (例如: .zip-tmexuusj) -> 对应代码中的 source-layer

      编辑

第二步:项目初始化

安装 Mapbox 依赖:

javascript 复制代码
npm install mapbox-gl
# 如果使用 TypeScript,建议安装类型声明
npm install @types/mapbox-gl -D

引入 CSS (在 main.ts 或组件内):

javascript 复制代码
import "mapbox-gl/dist/mapbox-gl.css";

第三步:核心代码实现

1. 地图初始化与汉化技巧

Mapbox 默认底图是英文的,这里分享一个遍历图层强制替换为中文字段的方法。

javascript 复制代码
<template>
  <div ref="mapContainer" class="map-container"></div>
</template>

<script setup lang="ts">
import mapboxgl from "mapbox-gl";
import "mapbox-gl/dist/mapbox-gl.css";
import { onMounted, onUnmounted, ref } from 'vue';

// 建议将 Token 放入 .env 文件中
mapboxgl.accessToken = "你的_Mapbox_Token";

const mapContainer = ref(null);
let map: mapboxgl.Map | null = null;

onMounted(() => {
  if (mapContainer.value) {
    map = new mapboxgl.Map({
      container: mapContainer.value,
      style: "mapbox://styles/mapbox/dark-v11", // 暗色系底图,突显数据
      center: [116.391, 39.906], // 北京中心点
      zoom: 10
    });

    map.on('load', () => {
      // --- 技巧:地图中文化 ---
      const layers = map.getStyle().layers;
      layers.forEach((layer) => {
        // 筛选出符号图层且包含文字的
        if (layer.type === 'symbol' && layer.layout['text-field']) {
          // 强制将文字字段优先设置为 "name_zh-Hans" (简体中文)
          // coalesce 表达式:如果中文不存在,则回退到默认 name
          map.setLayoutProperty(layer.id, 'text-field', [
            'coalesce',
            ['get', 'name_zh-Hans'],
            ['get', 'name']
          ]);
        }
      });
      
      // 接下来加载我们的数据...
      loadMyData();
    });
  }
});

onUnmounted(() => {
  map?.remove();
  map = null;
});
</script>

2. 加载矢量瓦片图层

这是最关键的一步。注意 source-layer 必须和你在 Mapbox Studio 里看到的一模一样,否则地图上什么都显示不出来。

javascript 复制代码
const loadMyData = () => {
  if (!map) return;

  // 1. 添加数据源 (Source)
  map.addSource('my-point-source', {
    type: 'vector', // 指定类型为矢量瓦片
    url: 'mapbox://gisdog.t63e74ab' // 替换为你自己的 Tileset ID
  });

  // 2. 添加图层 (Layer)
  map.addLayer({
    id: 'my-point-layer',
    type: 'circle', // 点数据通常用 circle 渲染,性能比 symbol 高
    source: 'my-point-source',
    // ⚠️ 高能预警:这里的 source-layer 必须去 Mapbox Studio 里复制,错一个字都不行
    'source-layer': '.zip-tmexuusj', 
    paint: {
      'circle-radius': 6, // 半径
      'circle-color': '#ff0000', // 红色圆点
      'circle-stroke-width': 1, // 描边宽度
      'circle-stroke-color': '#fff' // 白色描边
    }
  });
  
  // 初始化交互事件
  initInteraction();
};

3. 实现点击弹窗与鼠标样式

WebGIS 的灵魂在于交互。我们需要实现:鼠标移上去变"小手",点击弹出属性信息。

javascript 复制代码
const initInteraction = () => {
  if (!map) return;

  // 点击事件
  map.on('click', 'my-point-layer', (e) => {
    if (e.features && e.features[0]) {
      // 获取矢量瓦片中的属性数据 (dbf里的字段)
      const props = e.features[0].properties;
      console.log('点击获取属性:', props);

      // 创建弹窗
      new mapboxgl.Popup()
        .setLngLat(e.lngLat)
        // 这里假设 SHP 字段里有个 'name',请根据实际字段修改
        .setHTML(`
           <div style="font-weight:bold; color:#333;">
             ${props.name || '未知点位'}
           </div>
        `) 
        .addTo(map);
    }
  });

  // 鼠标移入 - 变手指
  map.on('mouseenter', 'my-point-layer', () => {
    map.getCanvas().style.cursor = 'pointer';
  });

  // 鼠标移出 - 恢复默认
  map.on('mouseleave', 'my-point-layer', () => {
    map.getCanvas().style.cursor = '';
  });
};

4. 样式美化 (CSS)

最后,给地图容器定宽定高,顺便处理一下 Mapbox 默认的 Logo(注:商业项目建议保留版权信息,遵守 Mapbox 条款)。

javascript 复制代码
<style scoped>
.map-container {
  width: 100%;
  height: 100vh; /* 全屏显示 */
  position: relative;
}

/* 隐藏左下角 mapbox logo */
:deep(.mapboxgl-ctrl-logo) {
  display: none !important;
} 

/* 隐藏右下角 mapbox 说明链接 */
:deep(.mapboxgl-ctrl) {
   display: none !important;
}
</style>

关于作者 & 合作

感谢阅读!我是专注于 WebGIS 前端可视化 的独立开发者。

如果你的团队正在做 智慧城市、数字孪生、流域监测 等项目,却苦于没有专业的 GIS 前端支持,或者需要快速输出高质量的 2D/3D 地图效果,欢迎联系我。

  • 核心技术栈: Vue3 / React + Mapbox / Cesium / Three.js + Springboot/FastAPI + PostgreSQL/PostGIS

  • 服务范围: WebGIS 项目整包开发、复杂地图交互实现。

目前已成立个人工作室、公司。可公对公,可开发票。

相比于找昂贵的大型外包公司,我能提供更灵活的沟通更高的性价比。无论是几十个页面的管理大屏,还是一个核心的地图功能模块,我都会像对待自己的产品一样打磨细节。

手头目前有档期,欢迎各位老板、PM 砸单。首单合作,价格好商量,保质保量交朋友!

相关推荐
Pilot-HJQ9 小时前
固定 Element UI 表格表头的方法(超简单)
vue.js·学习·css3·html5
Aliex_git9 小时前
性能优化 - Vue 日常实践优化
前端·javascript·vue.js·笔记·学习·性能优化
qq_316837759 小时前
Element-Plus el-table lazy 自动更新子列表
前端·vue.js·elementui
xiaoxue..9 小时前
把大模型装进自己电脑:Ollama 本地部署大模型完全指南
javascript·面试·node.js·大模型·ollama
Mr.app10 小时前
VUE:Ul列表内容自动向上滚动
vue.js
林恒smileZAZ10 小时前
Electron 的西天取经
前端·javascript·electron
Miketutu10 小时前
Flutter - 布局
开发语言·javascript·ecmascript
满栀58510 小时前
基于 jQuery 实现商品列表增删改查与数据统计
前端·javascript·jquery
web小白成长日记10 小时前
CSS 作用域隔离实战:React、Vue 与 Styled Components 的三种范式
前端·css·vue.js·react.js