全网最新:Vue 3 + TypeScript +MapBox GL JS实现 GeoJSON 数据上传与解析实战

🌍 功能概述

本项目实现了从文件上传到数据解析的完整 GeoJSON 处理流程,核心文件包括:
GeoJSONUpload.vue:负责文件上传交互
GeoJson.ts:提供数据解析与验证逻辑

该功能支持:

✅ 文件选择与预览 ✅ GeoJSON 格式验证 ✅ 异步解析与错误处理 ✅ 地图数据结构转换

前情提示:如何初始化地图在我上一篇博客

🧩 技术实现

1.事前准备

1.1安装依赖

npm install --save @turf/turf
npm install --save element-plus
npm install --save mapbox-gl

1.2版本信息

查看package.json文件

  "dependencies": {
    "@turf/turf": "^7.2.0",
    "element-plus": "^2.9.11",
    "mapbox-gl": "^3.12.0",
    "vue": "^3.5.13"
  },

1.3配置文件(config.ts)

export const MAPBOX_ACCESS_TOKEN = '用你自己的key,不会申请的可以找我';

// 定义公共配置项,并添加索引签名
export const LAYER_CONFIG: { [key: string]: any } = {
    Point: {
        type: 'circle',
        paint: {
            'circle-color': '#007cbf', // 蓝色
            'circle-radius': 7,
        },
    },
    MultiPoint: {
        type: 'circle',
        paint: {
            'circle-color': '#007cbf', // 蓝色
            'circle-radius': 7,
        },
    },
    LineString: {
        type: 'line',
        paint: {
            'line-color': '#007cbf', // 蓝色
            'line-width': 4,
        },
    },
    MultiLineString: {
        type: 'line',
        paint: {
            'line-color': '#007cbf', // 蓝色
            'line-width': 4,
        },
    },
    Polygon: {
        type: 'fill',
        paint: {
            'fill-color': '#007cbf', // 蓝色
            'fill-opacity': 0.5, // 半透明
        },
    },
    MultiPolygon: {
        type: 'fill',
        paint: {
            'fill-color': '#007cbf', // 蓝色
            'fill-opacity': 0.5, // 半透明
        },
    },
};


1.4工具类(GeoJsonUtil.ts)

import {centerOfMass} from "@turf/turf";

export const getGeoJsonCenter = (geoJson : Object) : [number,number] => {
    const mass = centerOfMass(geoJson);
    if(mass.geometry.coordinates){
        return mass.geometry.coordinates as [number,number]
    }
    debugger
    throw new Error("getGeoJsonCenter error");
}

2. 文件上传组件 (GeoJSONUpload.vue)

<template>
  <div class="geojson-upload">
    <el-upload
      action="#"
      :auto-upload="false"
      :show-file-list="false"
      :on-change="handleFileChange"
      accept=".geojson"
    >
      <el-button type="primary">上传 GeoJSON 文件</el-button>
    </el-upload>
  </div>
</template>

<script setup lang="ts">
import { ElMessage } from 'element-plus';
import {GeoJson} from "@/gis/data/handlers/GeoJson.ts";

const emit = defineEmits(['update:modelValue']);

// 处理文件上传
const handleFileChange = (file: any) => {
  const reader = new FileReader();
  reader.onload = (e) => {
    try {
      const geojsonData = JSON.parse(e.target?.result as string);
      // 校验 GeoJSON 数据格式
      if (!geojsonData || !geojsonData.type) {
        ElMessage.error('Invalid GeoJSON format. Missing "type" field.');
      }
      if (geojsonData.type !== 'Feature' && geojsonData.type !== 'FeatureCollection') {
        ElMessage.error('Invalid GeoJSON format. Expected "Feature" or "FeatureCollection".');
      }
      emit('update:modelValue', new GeoJson(geojsonData),file.name);
    } catch (error) {
      console.error('Error parsing GeoJSON:', error);
      ElMessage.error('上传的 GeoJSON 文件格式不正确,请检查后重新上传!');
    }
  };
  reader.readAsText(file.raw);
};

</script>

<style scoped>
.geojson-upload {
  margin-top: 10px;
}
</style>


3. 数据解析逻辑 (GeoJson.ts)

import type {Data} from "@/gis/data/Data.ts";
import {getType} from "@turf/turf";

export class GeoJson implements Data {
    data : string | Object;
    geometryType: string;
    constructor(data : string | Object) {
        this.data = data; // 默认初始化为空字符串或其他合适初始值
        this.geometryType = getType(this.toGeoJson() as any)
    }
    toGeoJson(): Object{
        if (typeof this.data === 'string'){
            return JSON.parse(this.data);
        }
        return this.data;
    }
    async getGeometryType(): Promise<string> {
        return await this.geometryType;
    }
}

4.数据接口类(Data.ts)

export interface Data  {
    data  : string | Object;
    geometryType : string;
    // read(data : any): any;
    toGeoJson(): Object;
    getGeometryType(): Promise<string>;
}

🚀 使用示例

<template>
    <!-- 文件上传按钮容器 -->
    <div class="upload-container">
      <GeoJSONUpload class="upload-item" @update:modelValue="handleDataUploaded" />
    </div>
    <!-- 地图。如何初始化地图在我上一篇博客 -->
    <div ref="mapContainer" class="map"></div>
</template>

<script setup lang="ts">
// 导入 Mapbox GL JS 相关依赖
import mapboxgl from "mapbox-gl";
import 'mapbox-gl/dist/mapbox-gl.css';
import { onMounted, ref} from 'vue';
import { ElMessage } from "element-plus";
import {MAPBOX_ACCESS_TOKEN, LAYER_CONFIG} from '@/config'; // 引入配置文件

// 图层列表相关变量
const layerList = ref<{name: string}[]>([]); // 图层列表

/**
 * 数据上传处理
 * @param data - 要添加的数据
 * @param name - 图层名称
 */
const handleDataUploaded = async <T extends Data>(data: T, name: string) => {
  // 检查名称唯一性
  if (layerList.value.some(layer => layer.name === name)) {
    ElMessage.warning('名称已存在');
    return;
  }
  // 添加图层
  layerList.value.push({ name });
  // 添加数据
  const sourceId = await addData(data, name);
  // 缩放至数据范围
  zoomTo(sourceId);
};

/**
 * 添加数据到地图
 * 根据数据类型创建数据源和图层
 * @param data - 要添加的数据
 * @param name
 * @returns 数据源 ID
 */
const addData = async (data: Data, name: string) => {
  const sourceId = `source-${name}`;
  if (instance.value) {
    const object = await data.toGeoJson();
    instance.value.addSource(sourceId, {type: 'geojson', data: object as any});
    sourceIds.value.push(sourceId);
  }
  const layerId = `layer-${name}`;
  if (instance.value) {
    const geometryType = await data.getGeometryType();
    if (geometryType === 'GeometryCollection') {
      ElMessage.warning('GeometryCollection类型暂不支持')
      //GeometryCollection 类型比较少用,一般arcgis中一个图层只能有一种要素类型
      throw new Error('GeometryCollection类型暂不支持')
    }
    let layerConfigElement = LAYER_CONFIG[geometryType];
    if (!layerConfigElement){
      ElMessage.warning('暂不支持该类型'+geometryType)
      throw new Error('暂不支持该类型')
    }
    instance.value.addLayer({id: layerId, source: sourceId, ...layerConfigElement});
    layerIds.value.push(layerId);
  }
  return sourceId;
};

/**
 * 定位到指定数据源的中心点
 * @param sourceId - 数据源 ID
 */
const zoomTo = (sourceId: string) => {
  if (instance.value) {
    const source = instance.value.getSource(sourceId) as mapboxgl.GeoJSONSource | undefined;
    if (source) {
      const [longitude, latitude] = getGeoJsonCenter(source.serialize().data as any);
      instance.value.flyTo({ center: [longitude, latitude], zoom: 12 });
    }
  }
};
</script>
<style scoped>
/* 文件上传按钮容器样式 */
.upload-container {
  position: absolute;
  top: 10px; /* 距离顶部的距离 */
  left: 10px; /* 距离左侧的距离 */
  display: flex; /* 使用 flex 布局 */
  gap: 10px; /* 按钮之间的间距 */
  z-index: 10; /* 确保在地图之上 */
  /* 文件上传按钮样式 */
  .upload-item {
    flex: 1; /* 确保按钮均匀分布 */
  }
}

/* 地图显示区域样式 */
.map {
  width: 100%;
  height: 100%; /* 为上传组件留出空间 */
}
</style>

最后提示:实现代码均为实际项目中复制,若有缺漏,留言或者私信

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值