@jorgmoritz/gis-manager 0.1.41 → 0.1.43

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vue/index.js CHANGED
@@ -371,6 +371,287 @@ var LayerManager = class {
371
371
  getTileset(id) {
372
372
  return this.tilesetIdMap.get(id);
373
373
  }
374
+ /**
375
+ * 从配置加载地图图层(正射影像、中文注记、地形)
376
+ * @param config 地图图层配置
377
+ * @param options 可选配置
378
+ * @returns 加载结果
379
+ */
380
+ async loadMapLayersFromConfig(config, options) {
381
+ const result = {
382
+ success: true,
383
+ errors: []
384
+ };
385
+ const {
386
+ tiandituToken = "f44fcec420c3eb1c56656e9a22dabb17",
387
+ subdomains = ["0", "1", "2", "3", "4", "5", "6", "7"],
388
+ maximumLevel = 18
389
+ } = options || {};
390
+ const C = this.CesiumNS;
391
+ const { imgUrl, ciaUrl, terrUrl } = config;
392
+ if (imgUrl) {
393
+ try {
394
+ const imageryProvider = this.createImageryProviderFromUrl(imgUrl, {
395
+ tiandituToken,
396
+ subdomains,
397
+ maximumLevel
398
+ });
399
+ if (imageryProvider) {
400
+ result.imageryLayer = this.viewer.imageryLayers.addImageryProvider(imageryProvider);
401
+ console.log("[LayerManager] \u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u6210\u529F:", imgUrl);
402
+ }
403
+ } catch (error) {
404
+ const errMsg = `\u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u5931\u8D25: ${error}`;
405
+ console.error("[LayerManager]", errMsg);
406
+ result.errors.push(errMsg);
407
+ }
408
+ }
409
+ if (ciaUrl) {
410
+ try {
411
+ const ciaProvider = this.createImageryProviderFromUrl(ciaUrl, {
412
+ tiandituToken,
413
+ subdomains,
414
+ maximumLevel
415
+ });
416
+ if (ciaProvider) {
417
+ result.annotationLayer = this.viewer.imageryLayers.addImageryProvider(ciaProvider);
418
+ console.log("[LayerManager] \u4E2D\u6587\u6CE8\u8BB0\u52A0\u8F7D\u6210\u529F:", ciaUrl);
419
+ }
420
+ } catch (error) {
421
+ const errMsg = `\u4E2D\u6587\u6CE8\u8BB0\u52A0\u8F7D\u5931\u8D25: ${error}`;
422
+ console.error("[LayerManager]", errMsg);
423
+ result.errors.push(errMsg);
424
+ }
425
+ }
426
+ if (terrUrl) {
427
+ try {
428
+ const terrainProvider = await C.CesiumTerrainProvider.fromUrl(terrUrl);
429
+ this.viewer.terrainProvider = terrainProvider;
430
+ result.terrainProvider = terrainProvider;
431
+ console.log("[LayerManager] \u5730\u5F62\u6570\u636E\u52A0\u8F7D\u6210\u529F:", terrUrl);
432
+ } catch (error) {
433
+ const errMsg = `\u5730\u5F62\u6570\u636E\u52A0\u8F7D\u5931\u8D25: ${error}`;
434
+ console.warn("[LayerManager]", errMsg);
435
+ result.errors.push(errMsg);
436
+ }
437
+ }
438
+ result.success = result.errors.length === 0;
439
+ return result;
440
+ }
441
+ /**
442
+ * 根据 URL 格式自动创建合适的 ImageryProvider
443
+ * @param url 影像 URL
444
+ * @param options 配置选项
445
+ */
446
+ createImageryProviderFromUrl(url, options) {
447
+ const C = this.CesiumNS;
448
+ const { tiandituToken, subdomains, maximumLevel } = options;
449
+ if (url.includes("{z}") || url.includes("{TileMatrix}")) {
450
+ return new C.UrlTemplateImageryProvider({
451
+ url,
452
+ subdomains,
453
+ tilingScheme: new C.WebMercatorTilingScheme(),
454
+ maximumLevel
455
+ });
456
+ }
457
+ if (url.includes("wmts")) {
458
+ const tkMatch = url.match(/tk=([^&]+)/);
459
+ const tk = tkMatch ? tkMatch[1] : tiandituToken;
460
+ return new C.WebMapTileServiceImageryProvider({
461
+ url: `https://t{s}.tianditu.gov.cn/img_w/wmts?tk=${tk}`,
462
+ subdomains,
463
+ layer: "img",
464
+ style: "default",
465
+ tileMatrixSetID: "w",
466
+ format: "image/jpeg",
467
+ tilingScheme: new C.WebMercatorTilingScheme(),
468
+ maximumLevel
469
+ });
470
+ }
471
+ return new C.UrlTemplateImageryProvider({
472
+ url,
473
+ subdomains,
474
+ tilingScheme: new C.WebMercatorTilingScheme(),
475
+ maximumLevel
476
+ });
477
+ }
478
+ /**
479
+ * 从 SliceJson 配置加载 TIF 切片数据(DOM 正射影像或 Terrain 地形)
480
+ * @param sliceJsonData JSON 字符串或已解析的对象
481
+ * @param options 加载选项
482
+ * @returns 加载结果
483
+ */
484
+ async loadFromSliceJson(sliceJsonData, options) {
485
+ const { layerId, flyTo = true, flyDuration = 2, opacity = 1 } = options || {};
486
+ let data;
487
+ try {
488
+ data = typeof sliceJsonData === "string" ? JSON.parse(sliceJsonData) : sliceJsonData;
489
+ } catch (error) {
490
+ return {
491
+ success: false,
492
+ type: "dom",
493
+ data: {},
494
+ error: `JSON \u89E3\u6790\u5931\u8D25: ${error}`
495
+ };
496
+ }
497
+ if (!data || !data.type || !data.urlroot) {
498
+ return {
499
+ success: false,
500
+ type: data?.type || "dom",
501
+ data: data || {},
502
+ error: "\u65E0\u6548\u7684 SliceJson \u6570\u636E\uFF1A\u7F3A\u5C11 type \u6216 urlroot"
503
+ };
504
+ }
505
+ if (data.type === "dom") {
506
+ return this.loadDomFromSliceJson(data, { layerId, flyTo, flyDuration, opacity });
507
+ } else if (data.type === "terrain") {
508
+ return this.loadTerrainFromSliceJson(data, { flyTo, flyDuration });
509
+ } else {
510
+ return {
511
+ success: false,
512
+ type: data.type,
513
+ data,
514
+ error: `\u4E0D\u652F\u6301\u7684\u6570\u636E\u7C7B\u578B: ${data.type}`
515
+ };
516
+ }
517
+ }
518
+ /**
519
+ * 加载 DOM 正射影像
520
+ */
521
+ async loadDomFromSliceJson(data, options) {
522
+ const { layerId, flyTo, flyDuration, opacity } = options;
523
+ const C = this.CesiumNS;
524
+ try {
525
+ const url = `${data.urlroot}/{z}/{x}/{reverseY}.png`;
526
+ let bounds = data.bounds;
527
+ if (bounds && bounds.length >= 4) {
528
+ if (Math.abs(bounds[1]) > 90 && Math.abs(bounds[2]) <= 90) {
529
+ bounds = [bounds[0], bounds[2], bounds[1], bounds[3]];
530
+ }
531
+ }
532
+ const rectangle = bounds ? C.Rectangle.fromDegrees(bounds[0], bounds[1], bounds[2], bounds[3]) : void 0;
533
+ const provider = new C.UrlTemplateImageryProvider({
534
+ url,
535
+ minimumLevel: data.minZoom,
536
+ maximumLevel: data.maxZoom,
537
+ rectangle
538
+ });
539
+ const layer = this.viewer.imageryLayers.addImageryProvider(provider);
540
+ layer.alpha = opacity;
541
+ const id = layerId || `slice_dom_${++layerIdSeq}`;
542
+ layer._customId = id;
543
+ this.imageryIdMap.set(id, layer);
544
+ const handle = { id, type: "imagery", dispose: () => this.remove(id) };
545
+ this.events.emit({ added: handle });
546
+ console.log("[LayerManager] DOM \u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u6210\u529F:", data.urlroot);
547
+ if (flyTo) {
548
+ await this.flyToSliceJsonTarget(data, flyDuration);
549
+ }
550
+ return {
551
+ success: true,
552
+ type: "dom",
553
+ handle,
554
+ imageryLayer: layer,
555
+ data
556
+ };
557
+ } catch (error) {
558
+ const errMsg = `DOM \u52A0\u8F7D\u5931\u8D25: ${error}`;
559
+ console.error("[LayerManager]", errMsg);
560
+ return {
561
+ success: false,
562
+ type: "dom",
563
+ data,
564
+ error: errMsg
565
+ };
566
+ }
567
+ }
568
+ /**
569
+ * 加载 Terrain 地形数据
570
+ */
571
+ async loadTerrainFromSliceJson(data, options) {
572
+ const { flyTo, flyDuration } = options;
573
+ const C = this.CesiumNS;
574
+ try {
575
+ const timestamp = Date.now();
576
+ const terrainUrl = `${data.urlroot}?t=${timestamp}`;
577
+ const terrainProvider = await C.CesiumTerrainProvider.fromUrl(terrainUrl, {
578
+ requestVertexNormals: true,
579
+ requestWaterMask: true
580
+ });
581
+ this.viewer.terrainProvider = terrainProvider;
582
+ this.terrainId = `slice_ter_${++layerIdSeq}`;
583
+ console.log("[LayerManager] Terrain \u5730\u5F62\u52A0\u8F7D\u6210\u529F:", data.urlroot);
584
+ if (flyTo) {
585
+ await new Promise((resolve) => setTimeout(resolve, 500));
586
+ await this.flyToSliceJsonTarget(data, flyDuration, true);
587
+ }
588
+ return {
589
+ success: true,
590
+ type: "terrain",
591
+ terrainProvider,
592
+ data
593
+ };
594
+ } catch (error) {
595
+ const errMsg = `Terrain \u52A0\u8F7D\u5931\u8D25: ${error}`;
596
+ console.error("[LayerManager]", errMsg);
597
+ return {
598
+ success: false,
599
+ type: "terrain",
600
+ data,
601
+ error: errMsg
602
+ };
603
+ }
604
+ }
605
+ /**
606
+ * 飞行到 SliceJson 数据的目标位置
607
+ */
608
+ async flyToSliceJsonTarget(data, duration, isVertical = false) {
609
+ const C = this.CesiumNS;
610
+ let centerLon;
611
+ let centerLat;
612
+ let cameraHeight = 2e3;
613
+ if (data.center && data.center.length >= 2 && (data.center[0] !== 0 || data.center[1] !== 0)) {
614
+ centerLon = data.center[0];
615
+ centerLat = data.center[1];
616
+ }
617
+ if (data.bounds && data.bounds.length >= 4) {
618
+ const bounds = data.bounds;
619
+ if (centerLon === void 0 || centerLat === void 0) {
620
+ centerLon = (bounds[0] + bounds[2]) / 2;
621
+ centerLat = (bounds[1] + bounds[3]) / 2;
622
+ }
623
+ const lonSpan = Math.abs(bounds[2] - bounds[0]);
624
+ const latSpan = Math.abs(bounds[3] - bounds[1]);
625
+ const maxSpan = Math.max(lonSpan, latSpan);
626
+ const heightMultiplier = isVertical ? 2 : 0.8;
627
+ cameraHeight = Math.max(maxSpan * 111e3 * heightMultiplier, 500);
628
+ }
629
+ if (centerLon === void 0 || centerLat === void 0) {
630
+ console.warn("[LayerManager] \u65E0\u6CD5\u83B7\u53D6\u6709\u6548\u7684\u4E2D\u5FC3\u70B9");
631
+ return;
632
+ }
633
+ let terrainHeight = 0;
634
+ try {
635
+ const terrainProvider = this.viewer.terrainProvider;
636
+ if (terrainProvider && !(terrainProvider instanceof C.EllipsoidTerrainProvider)) {
637
+ const positions = [C.Cartographic.fromDegrees(centerLon, centerLat)];
638
+ const sampledPositions = await C.sampleTerrainMostDetailed(terrainProvider, positions);
639
+ terrainHeight = sampledPositions[0]?.height || 0;
640
+ }
641
+ } catch (error) {
642
+ console.warn("[LayerManager] \u5730\u5F62\u91C7\u6837\u5931\u8D25:", error);
643
+ }
644
+ const finalHeight = terrainHeight + cameraHeight;
645
+ this.viewer.camera.flyTo({
646
+ destination: C.Cartesian3.fromDegrees(centerLon, centerLat, finalHeight),
647
+ orientation: {
648
+ heading: C.Math.toRadians(0),
649
+ pitch: C.Math.toRadians(isVertical ? -90 : -45),
650
+ roll: 0
651
+ },
652
+ duration
653
+ });
654
+ }
374
655
  };
375
656
 
376
657
  // src/core/toggle2D3D.ts
@@ -535,6 +816,65 @@ function toggle2D3D(ctx, options) {
535
816
  scene.requestRender?.();
536
817
  }
537
818
 
819
+ // src/utils/GeoBounds.ts
820
+ function calculateGeoBounds(points) {
821
+ if (!points || points.length === 0) {
822
+ return null;
823
+ }
824
+ let minLon = Infinity;
825
+ let maxLon = -Infinity;
826
+ let minLat = Infinity;
827
+ let maxLat = -Infinity;
828
+ let minAlt;
829
+ let maxAlt;
830
+ points.forEach((point) => {
831
+ minLon = Math.min(minLon, point.lon);
832
+ maxLon = Math.max(maxLon, point.lon);
833
+ minLat = Math.min(minLat, point.lat);
834
+ maxLat = Math.max(maxLat, point.lat);
835
+ if (point.alt !== void 0) {
836
+ if (minAlt === void 0 || point.alt < minAlt) {
837
+ minAlt = point.alt;
838
+ }
839
+ if (maxAlt === void 0 || point.alt > maxAlt) {
840
+ maxAlt = point.alt;
841
+ }
842
+ }
843
+ });
844
+ return {
845
+ west: minLon,
846
+ east: maxLon,
847
+ south: minLat,
848
+ north: maxLat,
849
+ centerLon: (minLon + maxLon) / 2,
850
+ centerLat: (minLat + maxLat) / 2,
851
+ width: maxLon - minLon,
852
+ height: maxLat - minLat,
853
+ minAlt,
854
+ maxAlt
855
+ };
856
+ }
857
+ function expandBounds(bounds, padding = 0.1) {
858
+ const lonPadding = bounds.width * padding;
859
+ const latPadding = bounds.height * padding;
860
+ const west = bounds.west - lonPadding;
861
+ const east = bounds.east + lonPadding;
862
+ const south = bounds.south - latPadding;
863
+ const north = bounds.north + latPadding;
864
+ return {
865
+ west,
866
+ east,
867
+ south,
868
+ north,
869
+ centerLon: (west + east) / 2,
870
+ centerLat: (south + north) / 2,
871
+ width: east - west,
872
+ height: north - south,
873
+ minAlt: bounds.minAlt,
874
+ maxAlt: bounds.maxAlt
875
+ };
876
+ }
877
+
538
878
  // src/core/CameraManager.ts
539
879
  var CameraManager = class {
540
880
  constructor(CesiumNS, viewer) {
@@ -623,12 +963,6 @@ var CameraManager = class {
623
963
  options
624
964
  );
625
965
  }
626
- /**
627
- * @deprecated Use toggle2D3D instead.
628
- */
629
- toggleBirdsEye3D(options) {
630
- this.toggle2D3D(options);
631
- }
632
966
  /**
633
967
  * 切换鸟瞰视角(垂直俯视)
634
968
  * 在当前3D场景中切换到垂直向下的俯视角度,并可选择使用正交投影
@@ -827,26 +1161,6 @@ var CameraManager = class {
827
1161
  const { destination, heading = 0, pitch = -Math.PI / 4, roll = 0 } = view;
828
1162
  this.viewer.camera.setView({ destination, orientation: { heading, pitch, roll } });
829
1163
  }
830
- // Debug helper: move camera to Chengdu, Sichuan
831
- moveToChengdu() {
832
- const lon = 104.0665;
833
- const lat = 30.5728;
834
- const height = 2e3;
835
- const dest = this.CesiumNS.Cartesian3.fromDegrees(lon, lat, height);
836
- this.viewer.trackedEntity = void 0;
837
- this.viewer.camera.lookAtTransform(this.CesiumNS.Matrix4.IDENTITY);
838
- this.viewer.camera.setView({
839
- destination: dest,
840
- orientation: { heading: 0, pitch: -0.8, roll: 0 }
841
- });
842
- }
843
- isitwork() {
844
- console.log("isitwork!!!!!!!!", this.viewer);
845
- console.log("isitwork cesium!!!!!!!!", this.CesiumNS);
846
- this.viewer.camera.setView({
847
- destination: this.CesiumNS.Cartesian3.fromDegrees(-117.16, 32.71, 15e3)
848
- });
849
- }
850
1164
  setViewEasy(args) {
851
1165
  const parsed = this.parseLonLat(args.destination);
852
1166
  const lon = parsed.lon;
@@ -1049,6 +1363,241 @@ var CameraManager = class {
1049
1363
  });
1050
1364
  });
1051
1365
  }
1366
+ // ==================== 截图相关方法 ====================
1367
+ /**
1368
+ * 飞行到边界框位置(用于截图)
1369
+ * 根据航点计算边界框,并将相机移动到合适位置以覆盖整个区域
1370
+ *
1371
+ * @param waypoints 航点数组
1372
+ * @param options 配置选项
1373
+ * @returns 计算出的相机高度
1374
+ *
1375
+ * @example
1376
+ * ```typescript
1377
+ * const waypoints = [
1378
+ * { lon: 104.0, lat: 30.5 },
1379
+ * { lon: 104.1, lat: 30.6 },
1380
+ * ];
1381
+ * await cameraManager.flyToBoundsForScreenshot(waypoints, { topDown: true });
1382
+ * ```
1383
+ */
1384
+ flyToBoundsForScreenshot(waypoints, options) {
1385
+ const bounds = calculateGeoBounds(waypoints);
1386
+ if (!bounds) return null;
1387
+ const {
1388
+ padding = 0.3,
1389
+ topDown = true,
1390
+ duration = 0,
1391
+ heightMultiplier = 1.05
1392
+ } = options || {};
1393
+ const C = this.CesiumNS;
1394
+ const camera = this.viewer.camera;
1395
+ const scene = this.viewer.scene;
1396
+ const expandedBounds = expandBounds(bounds, padding);
1397
+ const rectangle = C.Rectangle.fromDegrees(
1398
+ expandedBounds.west,
1399
+ expandedBounds.south,
1400
+ expandedBounds.east,
1401
+ expandedBounds.north
1402
+ );
1403
+ const cartographicCenter = C.Rectangle.center(rectangle);
1404
+ const canvas = scene.canvas;
1405
+ const aspectRatio = canvas.width / canvas.height;
1406
+ const earthRadius = 6371e3;
1407
+ const rectWidth = rectangle.width * earthRadius * Math.cos(cartographicCenter.latitude);
1408
+ const rectHeight = rectangle.height * earthRadius;
1409
+ const frustum = camera.frustum;
1410
+ const fovy = frustum.fovy || frustum.fov || C.Math.toRadians(60);
1411
+ const fovx = 2 * Math.atan(Math.tan(fovy / 2) * aspectRatio);
1412
+ const altitudeForHeight = rectHeight / 2 / Math.tan(fovy / 2);
1413
+ const altitudeForWidth = rectWidth / 2 / Math.tan(fovx / 2);
1414
+ const relativeAltitude = Math.max(altitudeForHeight, altitudeForWidth, 500) * heightMultiplier;
1415
+ const maxWaypointAlt = bounds.maxAlt ?? 0;
1416
+ const finalAltitude = maxWaypointAlt + relativeAltitude;
1417
+ console.log("[CameraManager] \u822A\u70B9\u6700\u5927\u6D77\u62D4:", maxWaypointAlt, "\u76F8\u5BF9\u9AD8\u5EA6:", relativeAltitude, "\u6700\u7EC8\u76F8\u673A\u9AD8\u5EA6:", finalAltitude);
1418
+ const destination = C.Cartesian3.fromRadians(
1419
+ cartographicCenter.longitude,
1420
+ cartographicCenter.latitude,
1421
+ finalAltitude
1422
+ );
1423
+ const orientation = topDown ? { heading: 0, pitch: C.Math.toRadians(-90), roll: 0 } : { heading: 0, pitch: C.Math.toRadians(-45), roll: 0 };
1424
+ if (duration > 0) {
1425
+ camera.flyTo({ destination, orientation, duration });
1426
+ } else {
1427
+ camera.setView({ destination, orientation });
1428
+ }
1429
+ return finalAltitude;
1430
+ }
1431
+ /**
1432
+ * 对当前 Cesium 场景进行截图
1433
+ *
1434
+ * @param options 截图选项
1435
+ * @returns 截图结果,包含 dataUrl、blob、file
1436
+ *
1437
+ * @example
1438
+ * ```typescript
1439
+ * const result = cameraManager.captureScreenshot();
1440
+ * console.log(result.dataUrl); // base64 图片
1441
+ * ```
1442
+ */
1443
+ captureScreenshot(options) {
1444
+ const { format = "image/png", quality = 0.92, fileName } = options || {};
1445
+ if (!this.viewer?.canvas) {
1446
+ throw new Error("\u65E0\u6548\u7684 Cesium Viewer \u5B9E\u4F8B\u6216 canvas \u4E0D\u5B58\u5728");
1447
+ }
1448
+ this.viewer.render();
1449
+ const canvas = this.viewer.canvas;
1450
+ const dataUrl = canvas.toDataURL(format, quality);
1451
+ const blob = this.dataURLtoBlob(dataUrl);
1452
+ const finalFileName = fileName || this.generateScreenshotFileName(format);
1453
+ const file = new File([blob], finalFileName, { type: format });
1454
+ return { dataUrl, blob, file };
1455
+ }
1456
+ /**
1457
+ * 调整视角并截图(异步)
1458
+ * 根据航点自动调整相机位置,等待渲染完成后截图
1459
+ *
1460
+ * @param options 截图选项
1461
+ * @returns 截图结果
1462
+ *
1463
+ * @example
1464
+ * ```typescript
1465
+ * const waypoints = [
1466
+ * { lon: 104.0, lat: 30.5 },
1467
+ * { lon: 104.1, lat: 30.6 },
1468
+ * ];
1469
+ * const result = await cameraManager.captureScreenshotWithView({
1470
+ * waypoints,
1471
+ * topDown: true,
1472
+ * padding: 0.3,
1473
+ * });
1474
+ * ```
1475
+ */
1476
+ async captureScreenshotWithView(options) {
1477
+ const {
1478
+ format = "image/png",
1479
+ quality = 0.92,
1480
+ fileName,
1481
+ waypoints,
1482
+ topDown = true,
1483
+ padding = 0.3,
1484
+ restoreCamera = true,
1485
+ renderDelay = 2e3
1486
+ } = options || {};
1487
+ if (!this.viewer?.scene) {
1488
+ throw new Error("\u65E0\u6548\u7684 Cesium Viewer \u5B9E\u4F8B");
1489
+ }
1490
+ const scene = this.viewer.scene;
1491
+ const camera = this.viewer.camera;
1492
+ const originalPosition = camera.position.clone();
1493
+ const originalHeading = camera.heading;
1494
+ const originalPitch = camera.pitch;
1495
+ const originalRoll = camera.roll;
1496
+ try {
1497
+ if (waypoints && waypoints.length > 0) {
1498
+ console.log("[CameraManager] \u5F00\u59CB\u8C03\u6574\u76F8\u673A\u4F4D\u7F6E\uFF0C\u822A\u70B9\u6570\u91CF:", waypoints.length);
1499
+ const altitude = this.flyToBoundsForScreenshot(waypoints, { padding, topDown, duration: 0 });
1500
+ console.log("[CameraManager] \u8BA1\u7B97\u7684\u76F8\u673A\u9AD8\u5EA6:", altitude);
1501
+ scene.requestRender();
1502
+ await this.waitForAnimationFrame();
1503
+ console.log("[CameraManager] \u7B49\u5F85\u74E6\u7247\u52A0\u8F7D (" + renderDelay + "ms)...");
1504
+ await this.delay(renderDelay);
1505
+ for (let i = 0; i < 20; i++) {
1506
+ scene.requestRender();
1507
+ await this.waitForAnimationFrame();
1508
+ if (i % 5 === 4) {
1509
+ await this.delay(100);
1510
+ }
1511
+ }
1512
+ console.log("[CameraManager] \u7B49\u5F85\u5B8C\u6210\uFF0C\u51C6\u5907\u622A\u56FE");
1513
+ }
1514
+ console.log("[CameraManager] \u5F00\u59CB\u622A\u56FE...");
1515
+ const result = await this.captureAfterRender({ format, quality, fileName });
1516
+ console.log("[CameraManager] \u622A\u56FE\u5B8C\u6210");
1517
+ if (restoreCamera && waypoints && waypoints.length > 0) {
1518
+ camera.setView({
1519
+ destination: originalPosition,
1520
+ orientation: {
1521
+ heading: originalHeading,
1522
+ pitch: originalPitch,
1523
+ roll: originalRoll
1524
+ }
1525
+ });
1526
+ }
1527
+ return result;
1528
+ } catch (error) {
1529
+ console.error("[CameraManager] \u622A\u56FE\u5931\u8D25:", error);
1530
+ if (restoreCamera) {
1531
+ camera.setView({
1532
+ destination: originalPosition,
1533
+ orientation: {
1534
+ heading: originalHeading,
1535
+ pitch: originalPitch,
1536
+ roll: originalRoll
1537
+ }
1538
+ });
1539
+ }
1540
+ throw error;
1541
+ }
1542
+ }
1543
+ /**
1544
+ * 等待渲染完成后截图
1545
+ */
1546
+ captureAfterRender(options) {
1547
+ return new Promise((resolve, reject) => {
1548
+ const { format = "image/png", quality = 0.92, fileName } = options;
1549
+ const scene = this.viewer.scene;
1550
+ const removeCallback = scene.postRender.addEventListener(() => {
1551
+ removeCallback();
1552
+ try {
1553
+ const canvas = scene.canvas;
1554
+ const dataUrl = canvas.toDataURL(format, quality);
1555
+ const blob = this.dataURLtoBlob(dataUrl);
1556
+ const finalFileName = fileName || this.generateScreenshotFileName(format);
1557
+ const file = new File([blob], finalFileName, { type: format });
1558
+ resolve({ dataUrl, blob, file });
1559
+ } catch (error) {
1560
+ reject(error);
1561
+ }
1562
+ });
1563
+ scene.requestRender();
1564
+ });
1565
+ }
1566
+ /**
1567
+ * 将 Base64 DataURL 转换为 Blob
1568
+ */
1569
+ dataURLtoBlob(dataUrl) {
1570
+ const arr = dataUrl.split(",");
1571
+ const mimeMatch = arr[0].match(/:(.*?);/);
1572
+ const mime = mimeMatch ? mimeMatch[1] : "image/png";
1573
+ const bstr = atob(arr[1]);
1574
+ let n = bstr.length;
1575
+ const u8arr = new Uint8Array(n);
1576
+ while (n--) {
1577
+ u8arr[n] = bstr.charCodeAt(n);
1578
+ }
1579
+ return new Blob([u8arr], { type: mime });
1580
+ }
1581
+ /**
1582
+ * 生成截图文件名
1583
+ */
1584
+ generateScreenshotFileName(format) {
1585
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1586
+ const ext = format === "image/jpeg" ? "jpg" : format === "image/webp" ? "webp" : "png";
1587
+ return `screenshot_${timestamp}.${ext}`;
1588
+ }
1589
+ /**
1590
+ * 延迟工具函数
1591
+ */
1592
+ delay(ms) {
1593
+ return new Promise((resolve) => setTimeout(resolve, ms));
1594
+ }
1595
+ /**
1596
+ * 等待下一个动画帧
1597
+ */
1598
+ waitForAnimationFrame() {
1599
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
1600
+ }
1052
1601
  };
1053
1602
 
1054
1603
  // src/core/SceneManager.ts
@@ -3533,6 +4082,28 @@ var VertexLabelManager = class {
3533
4082
  }
3534
4083
  if (this.labels[index]) {
3535
4084
  this.layer.entities.remove(this.labels[index]);
4085
+ this.labels[index] = void 0;
4086
+ }
4087
+ try {
4088
+ const entitiesToRemove = [];
4089
+ const allEntities = this.layer.entities.values;
4090
+ for (let i = 0; i < allEntities.length; i++) {
4091
+ const entity = allEntities[i];
4092
+ try {
4093
+ const props = entity.properties;
4094
+ const type = props?._type?.getValue?.() ?? props?._type;
4095
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4096
+ const vertexIndex = props?._vertexIndex?.getValue?.() ?? props?._vertexIndex;
4097
+ if (type === "vertex-label" && ownerId === this.ownerId && vertexIndex === index) {
4098
+ entitiesToRemove.push(entity);
4099
+ }
4100
+ } catch {
4101
+ }
4102
+ }
4103
+ for (const entity of entitiesToRemove) {
4104
+ this.layer.entities.remove(entity);
4105
+ }
4106
+ } catch {
3536
4107
  }
3537
4108
  this.labels[index] = this.createLabel(position, index, state);
3538
4109
  } catch (e) {
@@ -3550,8 +4121,8 @@ var VertexLabelManager = class {
3550
4121
  return;
3551
4122
  }
3552
4123
  const label = this.labels[index];
3553
- if (label && label.position) {
3554
- label.position.setValue(position);
4124
+ if (label) {
4125
+ label.position = position;
3555
4126
  }
3556
4127
  } catch (e) {
3557
4128
  console.error("Failed to update vertex label position:", e);
@@ -3559,12 +4130,11 @@ var VertexLabelManager = class {
3559
4130
  }
3560
4131
  /**
3561
4132
  * 插入新标签(在指定索引处)
4133
+ * 注意:此方法只更新内部数组,调用方需要随后调用 recreateAllLabels() 来重建所有标签
3562
4134
  */
3563
4135
  insertLabel(insertAt, position, state = "normal") {
3564
4136
  try {
3565
- const newLabel = this.createLabel(position, insertAt, state);
3566
- this.labels.splice(insertAt, 0, newLabel);
3567
- this.recreateAllLabels();
4137
+ this.labels.splice(insertAt, 0, void 0);
3568
4138
  } catch (e) {
3569
4139
  console.error("Failed to insert vertex label:", e);
3570
4140
  }
@@ -3594,6 +4164,27 @@ var VertexLabelManager = class {
3594
4164
  this.layer.entities.remove(this.labels[i]);
3595
4165
  }
3596
4166
  }
4167
+ try {
4168
+ const entitiesToRemove = [];
4169
+ const allEntities = this.layer.entities.values;
4170
+ for (let i = 0; i < allEntities.length; i++) {
4171
+ const entity = allEntities[i];
4172
+ try {
4173
+ const props = entity.properties;
4174
+ const type = props?._type?.getValue?.() ?? props?._type;
4175
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4176
+ if (type === "vertex-label" && ownerId === this.ownerId) {
4177
+ entitiesToRemove.push(entity);
4178
+ }
4179
+ } catch {
4180
+ }
4181
+ }
4182
+ for (const entity of entitiesToRemove) {
4183
+ this.layer.entities.remove(entity);
4184
+ }
4185
+ } catch {
4186
+ }
4187
+ this.labels = [];
3597
4188
  if (positions) {
3598
4189
  for (let i = 0; i < positions.length; i++) {
3599
4190
  const isEdited = editedIndices?.has(i) ?? false;
@@ -3622,6 +4213,26 @@ var VertexLabelManager = class {
3622
4213
  this.layer.entities.remove(label);
3623
4214
  }
3624
4215
  });
4216
+ try {
4217
+ const entitiesToRemove = [];
4218
+ const allEntities = this.layer.entities.values;
4219
+ for (let i = 0; i < allEntities.length; i++) {
4220
+ const entity = allEntities[i];
4221
+ try {
4222
+ const props = entity.properties;
4223
+ const type = props?._type?.getValue?.() ?? props?._type;
4224
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4225
+ if (type === "vertex-label" && ownerId === this.ownerId) {
4226
+ entitiesToRemove.push(entity);
4227
+ }
4228
+ } catch {
4229
+ }
4230
+ }
4231
+ for (const entity of entitiesToRemove) {
4232
+ this.layer.entities.remove(entity);
4233
+ }
4234
+ } catch {
4235
+ }
3625
4236
  this.labels = [];
3626
4237
  } catch (e) {
3627
4238
  console.error("Failed to destroy vertex labels:", e);
@@ -4252,7 +4863,7 @@ var VertexDragHandler = class {
4252
4863
  }
4253
4864
  };
4254
4865
 
4255
- // src/core/path-manager/utils/TerrainHeightQuery.ts
4866
+ // src/utils/TerrainHeightQuery.ts
4256
4867
  async function queryTerrainHeights(CesiumNS, viewer, positions) {
4257
4868
  const C = CesiumNS;
4258
4869
  if (!positions || positions.length === 0) {
@@ -6177,15 +6788,15 @@ function startPathEditing(CesiumNS, viewer, entityOrId, options) {
6177
6788
  } catch {
6178
6789
  }
6179
6790
  }
6180
- try {
6181
- vertexLabelManager.updateLabelPosition(i, newPosition);
6182
- } catch {
6183
- }
6184
6791
  try {
6185
6792
  createOrUpdateMarkerForIndex(i);
6186
6793
  } catch {
6187
6794
  }
6188
6795
  }
6796
+ try {
6797
+ vertexLabelManager.recreateAllLabels(positions, editedIndices, activeIndex);
6798
+ } catch {
6799
+ }
6189
6800
  currentAltitudeMode = newMode;
6190
6801
  quickEditOptions.altitudeMode = newMode;
6191
6802
  try {