scene-mapping-gis
v0.0.12
Published
统景 GIS 一体化组件库:一套组件同时搞定 2D(地图)与 3D(Cesium)场景
Maintainers
Readme
Scene Mapping GIS
基于 Vue 3、TypeScript、Leaflet 和 Cesium 的 GIS 组件库。
在线文档 · 2D 组件 · 3D Cesium · 2D/3D 场景切换
- 2D 地图:Leaflet
- 2D 地图源:天地图、OpenStreetMap、自定义 XYZ
- 2D 覆盖物:Marker、自定义瓦片图层、多边形绘制/编辑/删除
- 3D 地图:Cesium、业务 Marker、轨迹回放、动态墙和 3D Tiles
项目文档
在线文档:https://xincodingbreeze.github.io/scene-mapping-docs/
文档包含安装配置、2D 区域编辑、3D Cesium 场景、相机拾取、场景切换和类型参考。
问题反馈与功能建议:https://github.com/XinCodingBreeze/scene-mapping-docs/issues
npm run dev # 本地运行总 Demo
npm run docs:dev # 本地运行文档
npm run docs:build # 构建静态文档
npm run docs:preview # 预览构建结果
npm run docs:deploy # 发布到 GitHub Pages安装
直接安装组件库即可,Leaflet、Leaflet Draw 和 Cesium 会随包安装:
npm install scene-mapping-gisVue 是 peer dependency,由业务项目提供。scene-mapping-gis/2d 和 scene-mapping-gis/3d 是子入口,不是独立安装包。
3D 轨迹回放
CesiumTrackPlayback 支持播放、暂停、进度跳转、倍速、循环和高位相机跟随。轨迹点未提供 time 时,会在 duration 内均匀播放;提供相对秒数或绝对时间时,会保留真实采集间隔。
<CesiumMap>
<CesiumTrackPlayback
ref="playbackRef"
:points="trackPoints"
:duration="60"
:follow="true"
:follow-offset="[0, -3200, 3200]"
@progress="state = $event"
/>
</CesiumMap>完整参数、方法和实时示例请查看轨迹回放文档。
必须引入样式
建议在项目入口文件(例如 src/main.ts)中引入一次:
import "scene-mapping-gis/dist/style.css";该样式包含 Leaflet、Leaflet Draw、Cesium 及组件自身需要的 CSS。未引入时,地图控件、Marker 或绘制工具可能显示异常。
2D 快速开始
OpenStreetMap 不需要 Key,适合快速验证组件:
<template>
<div class="map-container">
<TMap provider="osm" @load="onMapLoad" @error="onMapError">
<TMapMarker :position="{ lng: 116.4074, lat: 39.9042 }" title="天安门" />
</TMap>
</div>
</template>
<script setup lang="ts">
import { TMap, TMapMarker } from "scene-mapping-gis/2d";
import "scene-mapping-gis/dist/style.css";
function onMapLoad(map: any) {
console.log("Leaflet Map", map);
}
function onMapError(error: Error) {
console.error("地图加载失败", error);
}
</script>
<style scoped>
.map-container {
width: 100%;
height: 600px;
}
</style>地图容器必须具有明确的宽度和高度,否则地图无法正常显示。
选择地图源
OpenStreetMap
<TMap provider="osm" />天地图
发布包不内置天地图 Key,需要由业务项目传入:
# .env.development
VITE_TDT_KEY=你的天地图Key<template>
<TMap provider="tianditu" :api-key="tdtKey" />
</template>
<script setup lang="ts">
import { TMap } from "scene-mapping-gis";
const tdtKey = import.meta.env.VITE_TDT_KEY;
</script>当前天地图模式默认加载:
img_w:影像底图cia_w:影像中文注记
如开启 VPN 后天地图无法访问,请在代理工具中将 *.tianditu.gov.cn 配置为直连。
自定义 XYZ
URL 必须包含 {z}、{x}、{y} 占位符:
<TMap provider="custom" url="https://example.com/tiles/{z}/{x}/{y}.png" />初始化地图
<TMap
provider="osm"
:options="{
center: { lng: 116.4074, lat: 39.9042 },
zoom: 12,
zoomControl: false,
attributionControl: false,
minZoom: 3,
maxZoom: 18,
}"
/>Marker
可拖拽 Marker
<template>
<TMap provider="osm">
<TMapMarker
ref="markerRef"
:position="position"
title="可拖拽标记"
draggable
@dragstart="onDragStart"
@move="onMarkerMove"
@dragend="onDragEnd"
/>
</TMap>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { TMap, TMapMarker } from "scene-mapping-gis";
import type { TMapMarkerEvent, TMapPoint } from "scene-mapping-gis";
const markerRef = ref<InstanceType<typeof TMapMarker> | null>(null);
const position = ref<TMapPoint>({ lng: 116.4074, lat: 39.9042 });
function onDragStart(event: TMapMarkerEvent) {
console.log("开始拖拽", event.position);
}
function onMarkerMove(event: TMapMarkerEvent) {
position.value = event.position;
}
function onDragEnd(event: TMapMarkerEvent) {
position.value = event.position;
console.log("最终位置", markerRef.value?.getPosition());
}
</script>Marker 暴露的方法:
markerRef.value?.getPosition();
markerRef.value?.setPosition([116.4, 39.9]);
markerRef.value?.enableDragging();
markerRef.value?.disableDragging();自定义 Marker 图标
图片应先通过 Vite 导入,不能直接使用相对源码文件的字符串路径:
<template>
<TMapMarker
:position="position"
:icon-url="markerImage"
:icon-size="[40, 60]"
:icon-anchor="[20, 60]"
/>
</template>
<script setup lang="ts">
import markerImage from "./assets/标记点.png";
</script>也支持通过 icon 传入完整的 Leaflet Icon 实例或图标配置对象。
多边形绘制、编辑和删除
TMapPolygonEditor 只负责多边形绘制、选中、编辑以及弹窗定位,不内置任何业务按钮和内容。通过 #popup 作用域插槽,可以传入任意 Vue 组件或 HTML 结构;地图拖动、缩放和多边形编辑时,弹窗会自动跟随。
使用内置工具栏
show-toolbar 默认为 true:
<template>
<div class="map-container">
<TMap provider="osm">
<TMapPolygonEditor
v-model="polygons"
@created="onCreated"
@edited="onEdited"
@deleted="onDeleted"
/>
</TMap>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { TMap, TMapPolygonEditor } from "scene-mapping-gis";
import type {
TMapPolygonChangeEvent,
TMapPolygonData,
} from "scene-mapping-gis";
const polygons = ref<TMapPolygonData[]>([]);
async function onCreated(event: TMapPolygonChangeEvent) {
console.log("本次新增", event.polygons);
console.log("当前全部", event.allPolygons);
await polygonApi.create(event.polygons[0]);
}
async function onEdited(event: TMapPolygonChangeEvent) {
console.log("本次编辑", event.polygons);
await polygonApi.update(event.polygons[0]);
}
async function onDeleted(event: TMapPolygonChangeEvent) {
console.log("本次删除", event.polygons);
await polygonApi.remove(event.polygons[0].id);
}
</script>多边形数据格式:
const polygons = [
{
id: "polygon-1",
points: [
{ lng: 116.4, lat: 39.9 },
{ lng: 116.42, lat: 39.9 },
{ lng: 116.41, lat: 39.92 },
],
// 可选:从主区域中扣除的洞,以及属于同一区域的独立岛。
holes: [
[
{ lng: 116.405, lat: 39.905 },
{ lng: 116.41, lat: 39.905 },
{ lng: 116.408, lat: 39.91 },
],
],
islands: [
[
{ lng: 116.43, lat: 39.91 },
{ lng: 116.435, lat: 39.91 },
{ lng: 116.433, lat: 39.915 },
],
],
pathOptions: {
color: "#2563eb",
fillColor: "#60a5fa",
fillOpacity: 0.35,
},
selectedPathOptions: {
color: "#f59e0b",
dashArray: "8, 8",
},
properties: {
name: "示例监管区域",
type: "重点区域",
owner: "张伟",
status: "正常",
},
},
];使用自定义按钮
下面的弹窗结构、字段、按钮和样式全部由业务侧维护:
<template>
<button @click="editorRef?.startDraw()">绘制</button>
<button @click="editorRef?.clear()">清空</button>
<div class="map-container">
<TMap provider="osm">
<TMapPolygonEditor
ref="editorRef"
v-model="polygons"
:options="polygonEditorOptions"
>
<template
#popup="{
polygon,
position,
editing,
edit,
remove,
save,
cancel,
close,
}"
>
<section class="business-popup">
<button @click="close">关闭</button>
<h3>{{ polygon.properties?.name }}</h3>
<p>锚点经度:{{ position.latLng.lng }}</p>
<p>顶点数量:{{ polygon.points.length }}</p>
<template v-if="editing">
<button @click="save">保存</button>
<button @click="cancel">取消</button>
</template>
<template v-else>
<button @click="edit">编辑</button>
<button @click="remove">删除</button>
</template>
</section>
</template>
</TMapPolygonEditor>
</TMap>
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { TMap, TMapPolygonEditor } from "scene-mapping-gis";
import type {
TMapPolygonData,
TMapPolygonEditorOptions,
} from "scene-mapping-gis";
const editorRef = ref<InstanceType<typeof TMapPolygonEditor> | null>(null);
const polygons = ref<TMapPolygonData[]>([
{
id: "polygon-1",
points: [
{ lng: 116.4, lat: 39.9 },
{ lng: 116.42, lat: 39.9 },
{ lng: 116.41, lat: 39.92 },
],
pathOptions: {
color: "#2563eb",
fillColor: "#60a5fa",
fillOpacity: 0.35,
},
selectedPathOptions: {
color: "#f59e0b",
dashArray: "8, 8",
},
properties: {
name: "示例监管区域",
type: "重点区域",
owner: "张伟",
status: "正常",
},
},
]);
const polygonEditorOptions: TMapPolygonEditorOptions = {
showToolbar: false,
showPopup: true,
// 默认 zh-CN,组件库会统一把 Leaflet Draw 的英文提示改成中文。
locale: "zh-CN",
// 可选:只覆盖你想自定义的 Leaflet Draw 文案。
drawLocal: {
draw: {
handlers: {
polyline: {
error: "<strong>错误:</strong>范围边界不能相交!",
},
},
},
},
popupOffset: [0, -12],
allowIntersection: false,
pathOptions: {
color: "#16a34a",
weight: 4,
fillColor: "#86efac",
fillOpacity: 0.35,
},
selectedPathOptions: {
color: "#f59e0b",
weight: 5,
dashArray: "8, 8",
},
};
</script>推荐只传一个 options JSON。其中 pathOptions 会同时应用于新绘制的多边形和通过 v-model 加载的已有多边形,selectedPathOptions 用于覆盖选中及编辑状态样式。两者都支持 Leaflet PathOptions 的全部字段。
当每个多边形需要不同颜色或携带业务字段时,直接把 pathOptions、selectedPathOptions 和 properties 放在对应的数组项中。单条数据的样式会覆盖 options 中的全局默认样式,编辑坐标时这些业务字段不会丢失。
将操作结果提交给后端
组件库不会直接调用任何业务接口,也不重复提供统一 change 事件。业务项目分别监听 created、edited、deleted 即可:新增或编辑时通常提交 event.polygons[0],删除时提交其中的 id;如果后端采用整体保存,则提交 event.allPolygons。取消编辑不会触发这些完成事件。
popup 插槽参数:
polygon:当前选中的多边形数据。position:包含锚点经纬度latLng和地图容器像素坐标containerPoint。editing:当前是否正在编辑顶点。edit/remove/save/cancel/close:操作当前多边形的方法。
如果不使用插槽,而是希望把弹层放到其他业务组件中,可以监听位置事件:
<TMapPolygonEditor
v-model="polygons"
:show-popup="false"
@select="onSelect"
@popup-position-change="onPopupPositionChange"
@deselect="onDeselect"
/>popup-position-change 会在地图移动、缩放和多边形顶点变化时持续返回最新位置。
多边形编辑器暴露的方法:
editorRef.value?.startDraw();
// 先选中一个区域,再向该区域添加洞或岛;未选中时返回 false。
editorRef.value?.startDrawHole();
editorRef.value?.startDrawIsland();
// 批量编辑和批量删除,兼容原有调用方式
editorRef.value?.startEdit();
editorRef.value?.startDelete();
editorRef.value?.saveAction();
editorRef.value?.stopAction();
// 根据业务 ID 选中、编辑或删除单个多边形
editorRef.value?.selectPolygon("polygon-1");
editorRef.value?.editPolygon("polygon-1");
editorRef.value?.deletePolygon("polygon-1");
editorRef.value?.clear();
editorRef.value?.getPolygons();绘制洞时,轮廓必须完整位于所选区域的主轮廓内部;不符合条件的结果不会写入 v-model。绘制岛时允许轮廓位于主区域之外。洞或岛绘制完成后会作为所属区域的一次修改触发 edited,业务侧仍可按原方式保存 event.polygons[0]。
组件在创建、编辑以及添加洞岛后,会通过 TMapPolygonData.area 返回最新净面积,单位固定为平方米。组件不内置 m²、km² 等展示格式,业务侧可按场景自行换算和保留小数位。
业务搜索与地图定位
组件库不请求天地图或其他行政区接口,也不维护省市区级联数据。业务层负责搜索并将接口结果转换为通用坐标,TMap 只负责地图定位:
<template>
<select v-model="criteria.province" @change="onAreaChange">
<option value="北京市">北京市</option>
</select>
<div class="map-container">
<TMap ref="mapRef" provider="tianditu" :api-key="tdtKey" />
</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
import { TMap } from "scene-mapping-gis";
import type { TMapExpose, TMapLocateOptions } from "scene-mapping-gis";
const tdtKey = "你的天地图 Key";
const mapRef = ref<TMapExpose | null>(null);
const criteria = ref({ province: "北京市", city: "", district: "" });
async function onAreaChange() {
// 接口请求属于业务层,可以使用天地图、业务后端或任意第三方服务。
const response = await areaApi.search(criteria.value);
const location: TMapLocateOptions = {
position: { lng: response.longitude, lat: response.latitude },
zoom: response.zoom,
// 如果接口返回区域边界,也可以传入 bounds,此时 locate 会优先显示完整边界。
bounds: response.bounds,
};
mapRef.value?.locate(location);
}
</script>TMap 还公开了以下基础地图方法:
mapRef.value?.setView({ lng: 116.4074, lat: 39.9042 }, 12);
mapRef.value?.panTo([116.4074, 39.9042]);
mapRef.value?.fitBounds([
{ lng: 115.42, lat: 39.44 },
{ lng: 117.5, lat: 41.06 },
]);
mapRef.value?.getCenter();
mapRef.value?.getZoom();
mapRef.value?.invalidateSize();
mapRef.value?.getMap(); // 获取 Leaflet 原生实例额外瓦片图层
TMapTileLayer 用于在基础地图上额外叠加一个瓦片图层:
<TMap provider="osm">
<TMapTileLayer
name="业务图层"
url="https://example.com/tiles/{z}/{x}/{y}.png"
:opacity="0.7"
/>
</TMap>3D Cesium
<template>
<div class="map-container">
<CesiumMap :ion-token="cesiumToken" @load="onViewerLoad">
<CesiumEntity :config="pointConfig" />
</CesiumMap>
</div>
</template>
<script setup lang="ts">
import { CesiumMap, CesiumEntity } from "scene-mapping-gis";
import type { CesiumEntityConfig } from "scene-mapping-gis/3d";
const cesiumToken = import.meta.env.VITE_CESIUM_TOKEN;
// 一个实体的类型、位置、图形和文字统一放在同一个配置对象中。
const pointConfig: CesiumEntityConfig = {
type: "point",
name: "天安门",
position: [116.4074, 39.9042, 100],
point: {
pixelSize: 20,
color: "#ef4444",
},
label: {
text: "天安门",
},
};
function onViewerLoad(viewer: any) {
console.log("Cesium Viewer", viewer);
}
</script>config.type 用来区分点、线和面,TypeScript 会提示当前类型可以填写哪些参数。点和线直接使用自身坐标渲染,面标签没有填写坐标时自动居中;需要改变文字方向时可设置 label.placement。旧版分散 Props 仍然兼容,推荐新代码统一使用 config。
常用配置支持直观写法,例如 alwaysVisible: true、pixelOffset: [0, -28]、heightReference: "clamp-to-ground",线和面的坐标也可以直接使用 [[经度, 纬度, 高度?], ...],无需手动创建 Cesium 枚举或 Cartesian 对象。
业务项目需要根据 Cesium 官方要求配置 CESIUM_BASE_URL 并部署 Workers、Assets、Widgets 等静态资源。本文档站已在 docs/.vitepress/config.mts 中处理 Cesium 静态资源,可作为接入参考。
2D/3D 切换
多地图源场景建议直接通过 v-if 切换 TMap 和 CesiumMap:
<template>
<button @click="mode = '2d'">2D</button>
<button @click="mode = '3d'">3D</button>
<TMap v-if="mode === '2d'" provider="osm" />
<CesiumMap v-else :ion-token="cesiumToken" />
</template>
<script setup lang="ts">
import { ref } from "vue";
import { TMap, CesiumMap } from "scene-mapping-gis";
const mode = ref<"2d" | "3d">("2d");
const cesiumToken = import.meta.env.VITE_CESIUM_TOKEN;
</script>npm 导出入口
仅使用 2D 或 3D 时,建议使用独立子入口,避免加载无关地图模块:
import { TMap, TMapMarker, TMapPolygonEditor } from "scene-mapping-gis/2d";
import { CesiumMap, CesiumEntity } from "scene-mapping-gis/3d";
import "scene-mapping-gis/dist/style.css";需要同时使用 2D、3D 或 SceneMap 时,也可以继续从根入口导入:
import { SceneMap, TMapMarker, CesiumEntity } from "scene-mapping-gis";本地开发
npm install
npm run dev
npm run docs:dev
npm run docs:build
npm run typecheck
npm run build注意事项
- 使用天地图前需在天地图开发者中心申请 Key。
- npm 包不保存任何天地图 Key 或 Cesium Token。
TrailMap已统一使用 Leaflet,可通过provider选择天地图、OpenStreetMap 或自定义 XYZ。- 安装或升级组件后若 Vite 出现
504 Outdated Optimize Dep,请停止开发服务、删除node_modules/.vite,再运行npm run dev -- --force。
License
MIT
