Skip to content

围栏与外部遮罩 ​

示例自带外环和洞的原创 GeoJSON。围栏支持 Polygon/MultiPolygon 与洞;外部遮罩作用于地面,不裁剪模型或 DOM,也不限制相机或提供进出检测。

在线运行此例 · 完整功能演示 · 下载全部源码

运行方法 ​

下载独立 Demo ZIP,解压后运行 npm ci、npm run dev,打开 http://127.0.0.1:5174/recipes.html?example=geofence。无需修改代码或配置 Key。文件路径为 src/examples/geofence.ts。

页面与初始化(所有示例共用) ​

以下文件已包含在 ZIP 中。复制到自己的 Vite 项目时应同时保留这些文件与本页的操作文件,入口是 recipes.html。安装 SDK/Cesium 并复制资源的步骤见快速开始。

完整 HTML 与入口
html
<!doctype html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <link rel="icon" href="data:," />
    <title>ShardX Map · 可运行教程</title>
  </head>
  <body>
    <nav id="recipes"></nav>
    <div id="actions"></div>
    <p id="status" role="status">正在加载…</p>
    <div id="map"></div>
    <script type="module" src="/src/recipes.ts"></script>
  </body>
</html>
ts
import { createContext } from './examples/runtime';

const recipes = {
  markers: ['点、图片、DOM', () => import('./examples/markers')],
  clusters: ['点聚合', () => import('./examples/clusters')],
  geometry: ['线面样式', () => import('./examples/geometry')],
  geofence: ['围栏与遮罩', () => import('./examples/geofence')],
  model: ['3D Tiles', () => import('./examples/model')],
  popups: ['弹窗与事件', () => import('./examples/popups')],
  camera: ['相机、底图、昼夜', () => import('./examples/camera')],
  lifecycle: ['多实例与销毁', () => import('./examples/lifecycle')],
} as const;
for (const [id, [title]] of Object.entries(recipes)) {
  const link = document.createElement('a');
  link.href = `?example=${id}`;
  link.textContent = title;
  document.querySelector('#recipes')!.append(link);
}
const id = new URLSearchParams(location.search).get('example') || 'markers';
async function start() {
  if (!(id in recipes)) throw new Error('未知示例');
  const ctx = await createContext();
  try {
    const [, load] = recipes[id as keyof typeof recipes];
    await (await load()).setup(ctx);
    ctx.report('示例已就绪');
    document.body.dataset.ready = id;
  } catch (error) {
    ctx.dispose();
    throw error;
  }
}
void start().catch((error) => {
  document.querySelector('#status')!.textContent = String(error);
});
地图初始化、按钮、异常与清理
ts
import { createMap } from '@shardx-map/sdk';
import type { MapInstance } from '@shardx-map/sdk';
import '@shardx-map/sdk/style.css';
import 'cesium/Build/Cesium/Widgets/widgets.css';
import './recipes.css';

export const view = { longitude: 114.05, latitude: 22.55, height: 50000, pitch: -90 };
export const assetBaseUrl = new URL(`${import.meta.env.BASE_URL}cesium/`, location.origin).href;
export type Context = Awaited<ReturnType<typeof createContext>>;

export async function createContext() {
  const cleanup: (() => void)[] = [];
  const report = (value: unknown) => {
    document.querySelector('#status')!.textContent = String(value);
  };
  const map = await createMap({
    container: 'map',
    assetBaseUrl,
    source: { type: 'naturalEarth' },
    view,
  });
  const dispose = () => {
    map.destroy();
    cleanup
      .splice(0)
      .reverse()
      .forEach((fn) => fn());
  };
  cleanup.push(
    map.on('error', (event) => {
      document.querySelector<HTMLElement>('#status')!.dataset.error = 'true';
      report(`${event.code}: ${event.message}`);
    }),
  );
  const action = (title: string, run: () => unknown | Promise<unknown>) => {
    const button = document.createElement('button');
    button.textContent = title;
    const click = async () => {
      button.disabled = true;
      try {
        await run();
      } catch (error) {
        document.querySelector<HTMLElement>('#status')!.dataset.error = 'true';
        report(error);
      } finally {
        button.disabled = false;
      }
    };
    button.addEventListener('click', click);
    document.querySelector('#actions')!.append(button);
    cleanup.push(() => {
      button.removeEventListener('click', click);
      button.remove();
    });
  };
  const track = (other: MapInstance) => cleanup.push(() => other.destroy());
  window.addEventListener('pagehide', dispose, { once: true });
  cleanup.push(() => window.removeEventListener('pagehide', dispose));
  if (import.meta.hot) import.meta.hot.dispose(dispose);
  return { map, cleanup, report, action, track, dispose };
}
完整 CSS
css
html,
body {
  margin: 0;
  height: 100%;
  font: 14px system-ui;
}
body {
  display: flex;
  flex-direction: column;
}
nav,
#actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  padding: 8px 16px;
}
nav a {
  color: #1765cc;
  padding: 4px;
}
button {
  cursor: pointer;
  padding: 8px 12px;
}
#status {
  margin: 0;
  padding: 8px 16px;
  background: #eef3fa;
}
#map {
  flex: 1;
  min-height: 400px;
  position: relative;
}
.map-label {
  color: white;
  background: #174e8c;
  padding: 8px 12px;
  border-radius: 6px;
}
.second-map {
  position: fixed;
  right: 12px;
  bottom: 48px;
  width: 40vw;
  height: 40vh;
  z-index: 10;
}

完整操作代码 ​

ts
import type { Context } from './runtime';

export async function setup({ map, action }: Context) {
  // Original sample with one hole, served over HTTP(S) from public/data.
  const url = new URL(`${import.meta.env.BASE_URL}data/fence.geojson`, location.origin).href;
  await map.addGeofenceLayer({
    id: 'fence',
    url,
    fillColor: '#2563eb',
    fillOpacity: 0.3,
    outlineWidth: 3,
    maskEnabled: true,
    maskColor: '#101828',
    maskOpacity: 0.65,
  });
  action('关闭遮罩', () => map.updateGeofenceStyle('fence', { maskEnabled: false }));
  action('开启遮罩', () =>
    map.updateGeofenceStyle('fence', { maskEnabled: true, maskOpacity: 0.8 }),
  );
  action('隐藏围栏', () => map.setLayerVisible('fence', false));
  action('显示围栏', () => map.setLayerVisible('fence', true));
  action('定位', () => map.flyToLayer('fence'));
}

完整数据(保存为 public/data/fence.geojson,通过 HTTP(S) 加载):

geojson
{
  "type": "Feature",
  "properties": { "name": "示例含洞围栏" },
  "geometry": {
    "type": "Polygon",
    "coordinates": [
      [
        [113.98, 22.49],
        [114.12, 22.49],
        [114.12, 22.61],
        [113.98, 22.61],
        [113.98, 22.49]
      ],
      [
        [114.03, 22.53],
        [114.03, 22.57],
        [114.07, 22.57],
        [114.07, 22.53],
        [114.03, 22.53]
      ]
    ]
  }
}

异步初始化和操作异常会显示到状态栏;SDK 的异步资源错误通过 error 订阅显示。离开页面或 HMR 时执行清理队列、取消订阅并销毁地图,同时移除业务 DOM 监听器。

0.1.0 · Cesium peer >=1.146.0 <1.147.0 · 外部部署验收单独记录