🌍 功能概述
本项目实现了从文件上传到数据解析的完整 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>