npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

scene-mapping-gis

v0.0.12

Published

统景 GIS 一体化组件库:一套组件同时搞定 2D(地图)与 3D(Cesium)场景

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