scene-mapping-gis

统景 GIS 一体化组件库:一套组件同时搞定 2D(地图)与 3D(Cesium)场景
1,960
Install
npm install scene-mapping-gis
Latest Version:1.0.0
Vue:3
License:MIT
Last Updated:Oct 21, 2026
Links: npm
Maintainer: haoxin123

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-gis

Vue 是 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

注意事项

  1. 使用天地图前需在天地图开发者中心申请 Key。
  2. npm 包不保存任何天地图 Key 或 Cesium Token。
  3. TrailMap 已统一使用 Leaflet,可通过 provider 选择天地图、OpenStreetMap 或自定义 XYZ。
  4. 安装或升级组件后若 Vite 出现 504 Outdated Optimize Dep,请停止开发服务、删除 node_modules/.vite,再运行 npm run dev -- --force。

License

MIT

Related Packages

wz-cesium-v3

飞行与3d地图整合,支持react和vue3

4,868
@akuno/vue-cesium

Vue 2.x components for CesiumJS.

968 1,917
agile-cesium

Vue 2.x components for CesiumJS.

420
b-map-viewer

面向 Vue 3 应用的 Cesium 地理信息可视化 SDK

808 10
vue-cesium

Vue 3.x components for CesiumJS.

162,999 1,916

Version History

Version Released Vue License
1.0.0 >=3.3.0 MIT
0.0.14 >=3.3.0 MIT
0.0.13 >=3.3.0 MIT
0.0.12 >=3.3.0 MIT
0.0.11 >=3.3.0 MIT