@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/index.js CHANGED
@@ -11,7 +11,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
11
11
  // package.json
12
12
  var package_default = {
13
13
  name: "@jorgmoritz/gis-manager",
14
- version: "0.1.41"};
14
+ version: "0.1.42"};
15
15
 
16
16
  // src/utils/version.ts
17
17
  var version = package_default.version;
@@ -415,6 +415,287 @@ var LayerManager = class {
415
415
  getTileset(id) {
416
416
  return this.tilesetIdMap.get(id);
417
417
  }
418
+ /**
419
+ * 从配置加载地图图层(正射影像、中文注记、地形)
420
+ * @param config 地图图层配置
421
+ * @param options 可选配置
422
+ * @returns 加载结果
423
+ */
424
+ async loadMapLayersFromConfig(config, options) {
425
+ const result = {
426
+ success: true,
427
+ errors: []
428
+ };
429
+ const {
430
+ tiandituToken = "f44fcec420c3eb1c56656e9a22dabb17",
431
+ subdomains = ["0", "1", "2", "3", "4", "5", "6", "7"],
432
+ maximumLevel = 18
433
+ } = options || {};
434
+ const C = this.CesiumNS;
435
+ const { imgUrl, ciaUrl, terrUrl } = config;
436
+ if (imgUrl) {
437
+ try {
438
+ const imageryProvider = this.createImageryProviderFromUrl(imgUrl, {
439
+ tiandituToken,
440
+ subdomains,
441
+ maximumLevel
442
+ });
443
+ if (imageryProvider) {
444
+ result.imageryLayer = this.viewer.imageryLayers.addImageryProvider(imageryProvider);
445
+ console.log("[LayerManager] \u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u6210\u529F:", imgUrl);
446
+ }
447
+ } catch (error) {
448
+ const errMsg = `\u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u5931\u8D25: ${error}`;
449
+ console.error("[LayerManager]", errMsg);
450
+ result.errors.push(errMsg);
451
+ }
452
+ }
453
+ if (ciaUrl) {
454
+ try {
455
+ const ciaProvider = this.createImageryProviderFromUrl(ciaUrl, {
456
+ tiandituToken,
457
+ subdomains,
458
+ maximumLevel
459
+ });
460
+ if (ciaProvider) {
461
+ result.annotationLayer = this.viewer.imageryLayers.addImageryProvider(ciaProvider);
462
+ console.log("[LayerManager] \u4E2D\u6587\u6CE8\u8BB0\u52A0\u8F7D\u6210\u529F:", ciaUrl);
463
+ }
464
+ } catch (error) {
465
+ const errMsg = `\u4E2D\u6587\u6CE8\u8BB0\u52A0\u8F7D\u5931\u8D25: ${error}`;
466
+ console.error("[LayerManager]", errMsg);
467
+ result.errors.push(errMsg);
468
+ }
469
+ }
470
+ if (terrUrl) {
471
+ try {
472
+ const terrainProvider = await C.CesiumTerrainProvider.fromUrl(terrUrl);
473
+ this.viewer.terrainProvider = terrainProvider;
474
+ result.terrainProvider = terrainProvider;
475
+ console.log("[LayerManager] \u5730\u5F62\u6570\u636E\u52A0\u8F7D\u6210\u529F:", terrUrl);
476
+ } catch (error) {
477
+ const errMsg = `\u5730\u5F62\u6570\u636E\u52A0\u8F7D\u5931\u8D25: ${error}`;
478
+ console.warn("[LayerManager]", errMsg);
479
+ result.errors.push(errMsg);
480
+ }
481
+ }
482
+ result.success = result.errors.length === 0;
483
+ return result;
484
+ }
485
+ /**
486
+ * 根据 URL 格式自动创建合适的 ImageryProvider
487
+ * @param url 影像 URL
488
+ * @param options 配置选项
489
+ */
490
+ createImageryProviderFromUrl(url, options) {
491
+ const C = this.CesiumNS;
492
+ const { tiandituToken, subdomains, maximumLevel } = options;
493
+ if (url.includes("{z}") || url.includes("{TileMatrix}")) {
494
+ return new C.UrlTemplateImageryProvider({
495
+ url,
496
+ subdomains,
497
+ tilingScheme: new C.WebMercatorTilingScheme(),
498
+ maximumLevel
499
+ });
500
+ }
501
+ if (url.includes("wmts")) {
502
+ const tkMatch = url.match(/tk=([^&]+)/);
503
+ const tk = tkMatch ? tkMatch[1] : tiandituToken;
504
+ return new C.WebMapTileServiceImageryProvider({
505
+ url: `https://t{s}.tianditu.gov.cn/img_w/wmts?tk=${tk}`,
506
+ subdomains,
507
+ layer: "img",
508
+ style: "default",
509
+ tileMatrixSetID: "w",
510
+ format: "image/jpeg",
511
+ tilingScheme: new C.WebMercatorTilingScheme(),
512
+ maximumLevel
513
+ });
514
+ }
515
+ return new C.UrlTemplateImageryProvider({
516
+ url,
517
+ subdomains,
518
+ tilingScheme: new C.WebMercatorTilingScheme(),
519
+ maximumLevel
520
+ });
521
+ }
522
+ /**
523
+ * 从 SliceJson 配置加载 TIF 切片数据(DOM 正射影像或 Terrain 地形)
524
+ * @param sliceJsonData JSON 字符串或已解析的对象
525
+ * @param options 加载选项
526
+ * @returns 加载结果
527
+ */
528
+ async loadFromSliceJson(sliceJsonData, options) {
529
+ const { layerId, flyTo = true, flyDuration = 2, opacity = 1 } = options || {};
530
+ let data;
531
+ try {
532
+ data = typeof sliceJsonData === "string" ? JSON.parse(sliceJsonData) : sliceJsonData;
533
+ } catch (error) {
534
+ return {
535
+ success: false,
536
+ type: "dom",
537
+ data: {},
538
+ error: `JSON \u89E3\u6790\u5931\u8D25: ${error}`
539
+ };
540
+ }
541
+ if (!data || !data.type || !data.urlroot) {
542
+ return {
543
+ success: false,
544
+ type: data?.type || "dom",
545
+ data: data || {},
546
+ error: "\u65E0\u6548\u7684 SliceJson \u6570\u636E\uFF1A\u7F3A\u5C11 type \u6216 urlroot"
547
+ };
548
+ }
549
+ if (data.type === "dom") {
550
+ return this.loadDomFromSliceJson(data, { layerId, flyTo, flyDuration, opacity });
551
+ } else if (data.type === "terrain") {
552
+ return this.loadTerrainFromSliceJson(data, { flyTo, flyDuration });
553
+ } else {
554
+ return {
555
+ success: false,
556
+ type: data.type,
557
+ data,
558
+ error: `\u4E0D\u652F\u6301\u7684\u6570\u636E\u7C7B\u578B: ${data.type}`
559
+ };
560
+ }
561
+ }
562
+ /**
563
+ * 加载 DOM 正射影像
564
+ */
565
+ async loadDomFromSliceJson(data, options) {
566
+ const { layerId, flyTo, flyDuration, opacity } = options;
567
+ const C = this.CesiumNS;
568
+ try {
569
+ const url = `${data.urlroot}/{z}/{x}/{reverseY}.png`;
570
+ let bounds = data.bounds;
571
+ if (bounds && bounds.length >= 4) {
572
+ if (Math.abs(bounds[1]) > 90 && Math.abs(bounds[2]) <= 90) {
573
+ bounds = [bounds[0], bounds[2], bounds[1], bounds[3]];
574
+ }
575
+ }
576
+ const rectangle = bounds ? C.Rectangle.fromDegrees(bounds[0], bounds[1], bounds[2], bounds[3]) : void 0;
577
+ const provider = new C.UrlTemplateImageryProvider({
578
+ url,
579
+ minimumLevel: data.minZoom,
580
+ maximumLevel: data.maxZoom,
581
+ rectangle
582
+ });
583
+ const layer = this.viewer.imageryLayers.addImageryProvider(provider);
584
+ layer.alpha = opacity;
585
+ const id = layerId || `slice_dom_${++layerIdSeq}`;
586
+ layer._customId = id;
587
+ this.imageryIdMap.set(id, layer);
588
+ const handle = { id, type: "imagery", dispose: () => this.remove(id) };
589
+ this.events.emit({ added: handle });
590
+ console.log("[LayerManager] DOM \u6B63\u5C04\u5F71\u50CF\u52A0\u8F7D\u6210\u529F:", data.urlroot);
591
+ if (flyTo) {
592
+ await this.flyToSliceJsonTarget(data, flyDuration);
593
+ }
594
+ return {
595
+ success: true,
596
+ type: "dom",
597
+ handle,
598
+ imageryLayer: layer,
599
+ data
600
+ };
601
+ } catch (error) {
602
+ const errMsg = `DOM \u52A0\u8F7D\u5931\u8D25: ${error}`;
603
+ console.error("[LayerManager]", errMsg);
604
+ return {
605
+ success: false,
606
+ type: "dom",
607
+ data,
608
+ error: errMsg
609
+ };
610
+ }
611
+ }
612
+ /**
613
+ * 加载 Terrain 地形数据
614
+ */
615
+ async loadTerrainFromSliceJson(data, options) {
616
+ const { flyTo, flyDuration } = options;
617
+ const C = this.CesiumNS;
618
+ try {
619
+ const timestamp = Date.now();
620
+ const terrainUrl = `${data.urlroot}?t=${timestamp}`;
621
+ const terrainProvider = await C.CesiumTerrainProvider.fromUrl(terrainUrl, {
622
+ requestVertexNormals: true,
623
+ requestWaterMask: true
624
+ });
625
+ this.viewer.terrainProvider = terrainProvider;
626
+ this.terrainId = `slice_ter_${++layerIdSeq}`;
627
+ console.log("[LayerManager] Terrain \u5730\u5F62\u52A0\u8F7D\u6210\u529F:", data.urlroot);
628
+ if (flyTo) {
629
+ await new Promise((resolve) => setTimeout(resolve, 500));
630
+ await this.flyToSliceJsonTarget(data, flyDuration, true);
631
+ }
632
+ return {
633
+ success: true,
634
+ type: "terrain",
635
+ terrainProvider,
636
+ data
637
+ };
638
+ } catch (error) {
639
+ const errMsg = `Terrain \u52A0\u8F7D\u5931\u8D25: ${error}`;
640
+ console.error("[LayerManager]", errMsg);
641
+ return {
642
+ success: false,
643
+ type: "terrain",
644
+ data,
645
+ error: errMsg
646
+ };
647
+ }
648
+ }
649
+ /**
650
+ * 飞行到 SliceJson 数据的目标位置
651
+ */
652
+ async flyToSliceJsonTarget(data, duration, isVertical = false) {
653
+ const C = this.CesiumNS;
654
+ let centerLon;
655
+ let centerLat;
656
+ let cameraHeight = 2e3;
657
+ if (data.center && data.center.length >= 2 && (data.center[0] !== 0 || data.center[1] !== 0)) {
658
+ centerLon = data.center[0];
659
+ centerLat = data.center[1];
660
+ }
661
+ if (data.bounds && data.bounds.length >= 4) {
662
+ const bounds = data.bounds;
663
+ if (centerLon === void 0 || centerLat === void 0) {
664
+ centerLon = (bounds[0] + bounds[2]) / 2;
665
+ centerLat = (bounds[1] + bounds[3]) / 2;
666
+ }
667
+ const lonSpan = Math.abs(bounds[2] - bounds[0]);
668
+ const latSpan = Math.abs(bounds[3] - bounds[1]);
669
+ const maxSpan = Math.max(lonSpan, latSpan);
670
+ const heightMultiplier = isVertical ? 2 : 0.8;
671
+ cameraHeight = Math.max(maxSpan * 111e3 * heightMultiplier, 500);
672
+ }
673
+ if (centerLon === void 0 || centerLat === void 0) {
674
+ console.warn("[LayerManager] \u65E0\u6CD5\u83B7\u53D6\u6709\u6548\u7684\u4E2D\u5FC3\u70B9");
675
+ return;
676
+ }
677
+ let terrainHeight = 0;
678
+ try {
679
+ const terrainProvider = this.viewer.terrainProvider;
680
+ if (terrainProvider && !(terrainProvider instanceof C.EllipsoidTerrainProvider)) {
681
+ const positions = [C.Cartographic.fromDegrees(centerLon, centerLat)];
682
+ const sampledPositions = await C.sampleTerrainMostDetailed(terrainProvider, positions);
683
+ terrainHeight = sampledPositions[0]?.height || 0;
684
+ }
685
+ } catch (error) {
686
+ console.warn("[LayerManager] \u5730\u5F62\u91C7\u6837\u5931\u8D25:", error);
687
+ }
688
+ const finalHeight = terrainHeight + cameraHeight;
689
+ this.viewer.camera.flyTo({
690
+ destination: C.Cartesian3.fromDegrees(centerLon, centerLat, finalHeight),
691
+ orientation: {
692
+ heading: C.Math.toRadians(0),
693
+ pitch: C.Math.toRadians(isVertical ? -90 : -45),
694
+ roll: 0
695
+ },
696
+ duration
697
+ });
698
+ }
418
699
  };
419
700
 
420
701
  // src/core/toggle2D3D.ts
@@ -579,6 +860,114 @@ function toggle2D3D(ctx, options) {
579
860
  scene.requestRender?.();
580
861
  }
581
862
 
863
+ // src/utils/GeoBounds.ts
864
+ function calculateGeoBounds(points) {
865
+ if (!points || points.length === 0) {
866
+ return null;
867
+ }
868
+ let minLon = Infinity;
869
+ let maxLon = -Infinity;
870
+ let minLat = Infinity;
871
+ let maxLat = -Infinity;
872
+ let minAlt;
873
+ let maxAlt;
874
+ points.forEach((point) => {
875
+ minLon = Math.min(minLon, point.lon);
876
+ maxLon = Math.max(maxLon, point.lon);
877
+ minLat = Math.min(minLat, point.lat);
878
+ maxLat = Math.max(maxLat, point.lat);
879
+ if (point.alt !== void 0) {
880
+ if (minAlt === void 0 || point.alt < minAlt) {
881
+ minAlt = point.alt;
882
+ }
883
+ if (maxAlt === void 0 || point.alt > maxAlt) {
884
+ maxAlt = point.alt;
885
+ }
886
+ }
887
+ });
888
+ return {
889
+ west: minLon,
890
+ east: maxLon,
891
+ south: minLat,
892
+ north: maxLat,
893
+ centerLon: (minLon + maxLon) / 2,
894
+ centerLat: (minLat + maxLat) / 2,
895
+ width: maxLon - minLon,
896
+ height: maxLat - minLat,
897
+ minAlt,
898
+ maxAlt
899
+ };
900
+ }
901
+ function calculateBoundsDiagonal(bounds) {
902
+ const earthRadius = 6371e3;
903
+ const latRad = bounds.centerLat * Math.PI / 180;
904
+ const widthMeters = bounds.width * (Math.PI / 180) * earthRadius * Math.cos(latRad);
905
+ const heightMeters = bounds.height * (Math.PI / 180) * earthRadius;
906
+ return Math.sqrt(widthMeters * widthMeters + heightMeters * heightMeters);
907
+ }
908
+ function expandBounds(bounds, padding = 0.1) {
909
+ const lonPadding = bounds.width * padding;
910
+ const latPadding = bounds.height * padding;
911
+ const west = bounds.west - lonPadding;
912
+ const east = bounds.east + lonPadding;
913
+ const south = bounds.south - latPadding;
914
+ const north = bounds.north + latPadding;
915
+ return {
916
+ west,
917
+ east,
918
+ south,
919
+ north,
920
+ centerLon: (west + east) / 2,
921
+ centerLat: (south + north) / 2,
922
+ width: east - west,
923
+ height: north - south,
924
+ minAlt: bounds.minAlt,
925
+ maxAlt: bounds.maxAlt
926
+ };
927
+ }
928
+ function isPointInBounds(point, bounds) {
929
+ return point.lon >= bounds.west && point.lon <= bounds.east && point.lat >= bounds.south && point.lat <= bounds.north;
930
+ }
931
+ function mergeBounds(boundsArray) {
932
+ if (!boundsArray || boundsArray.length === 0) {
933
+ return null;
934
+ }
935
+ let minLon = Infinity;
936
+ let maxLon = -Infinity;
937
+ let minLat = Infinity;
938
+ let maxLat = -Infinity;
939
+ let minAlt;
940
+ let maxAlt;
941
+ boundsArray.forEach((bounds) => {
942
+ minLon = Math.min(minLon, bounds.west);
943
+ maxLon = Math.max(maxLon, bounds.east);
944
+ minLat = Math.min(minLat, bounds.south);
945
+ maxLat = Math.max(maxLat, bounds.north);
946
+ if (bounds.minAlt !== void 0) {
947
+ if (minAlt === void 0 || bounds.minAlt < minAlt) {
948
+ minAlt = bounds.minAlt;
949
+ }
950
+ }
951
+ if (bounds.maxAlt !== void 0) {
952
+ if (maxAlt === void 0 || bounds.maxAlt > maxAlt) {
953
+ maxAlt = bounds.maxAlt;
954
+ }
955
+ }
956
+ });
957
+ return {
958
+ west: minLon,
959
+ east: maxLon,
960
+ south: minLat,
961
+ north: maxLat,
962
+ centerLon: (minLon + maxLon) / 2,
963
+ centerLat: (minLat + maxLat) / 2,
964
+ width: maxLon - minLon,
965
+ height: maxLat - minLat,
966
+ minAlt,
967
+ maxAlt
968
+ };
969
+ }
970
+
582
971
  // src/core/CameraManager.ts
583
972
  var CameraManager = class {
584
973
  constructor(CesiumNS, viewer) {
@@ -667,12 +1056,6 @@ var CameraManager = class {
667
1056
  options
668
1057
  );
669
1058
  }
670
- /**
671
- * @deprecated Use toggle2D3D instead.
672
- */
673
- toggleBirdsEye3D(options) {
674
- this.toggle2D3D(options);
675
- }
676
1059
  /**
677
1060
  * 切换鸟瞰视角(垂直俯视)
678
1061
  * 在当前3D场景中切换到垂直向下的俯视角度,并可选择使用正交投影
@@ -871,26 +1254,6 @@ var CameraManager = class {
871
1254
  const { destination, heading = 0, pitch = -Math.PI / 4, roll = 0 } = view;
872
1255
  this.viewer.camera.setView({ destination, orientation: { heading, pitch, roll } });
873
1256
  }
874
- // Debug helper: move camera to Chengdu, Sichuan
875
- moveToChengdu() {
876
- const lon = 104.0665;
877
- const lat = 30.5728;
878
- const height = 2e3;
879
- const dest = this.CesiumNS.Cartesian3.fromDegrees(lon, lat, height);
880
- this.viewer.trackedEntity = void 0;
881
- this.viewer.camera.lookAtTransform(this.CesiumNS.Matrix4.IDENTITY);
882
- this.viewer.camera.setView({
883
- destination: dest,
884
- orientation: { heading: 0, pitch: -0.8, roll: 0 }
885
- });
886
- }
887
- isitwork() {
888
- console.log("isitwork!!!!!!!!", this.viewer);
889
- console.log("isitwork cesium!!!!!!!!", this.CesiumNS);
890
- this.viewer.camera.setView({
891
- destination: this.CesiumNS.Cartesian3.fromDegrees(-117.16, 32.71, 15e3)
892
- });
893
- }
894
1257
  setViewEasy(args) {
895
1258
  const parsed = this.parseLonLat(args.destination);
896
1259
  const lon = parsed.lon;
@@ -1093,6 +1456,241 @@ var CameraManager = class {
1093
1456
  });
1094
1457
  });
1095
1458
  }
1459
+ // ==================== 截图相关方法 ====================
1460
+ /**
1461
+ * 飞行到边界框位置(用于截图)
1462
+ * 根据航点计算边界框,并将相机移动到合适位置以覆盖整个区域
1463
+ *
1464
+ * @param waypoints 航点数组
1465
+ * @param options 配置选项
1466
+ * @returns 计算出的相机高度
1467
+ *
1468
+ * @example
1469
+ * ```typescript
1470
+ * const waypoints = [
1471
+ * { lon: 104.0, lat: 30.5 },
1472
+ * { lon: 104.1, lat: 30.6 },
1473
+ * ];
1474
+ * await cameraManager.flyToBoundsForScreenshot(waypoints, { topDown: true });
1475
+ * ```
1476
+ */
1477
+ flyToBoundsForScreenshot(waypoints, options) {
1478
+ const bounds = calculateGeoBounds(waypoints);
1479
+ if (!bounds) return null;
1480
+ const {
1481
+ padding = 0.3,
1482
+ topDown = true,
1483
+ duration = 0,
1484
+ heightMultiplier = 1.05
1485
+ } = options || {};
1486
+ const C = this.CesiumNS;
1487
+ const camera = this.viewer.camera;
1488
+ const scene = this.viewer.scene;
1489
+ const expandedBounds = expandBounds(bounds, padding);
1490
+ const rectangle = C.Rectangle.fromDegrees(
1491
+ expandedBounds.west,
1492
+ expandedBounds.south,
1493
+ expandedBounds.east,
1494
+ expandedBounds.north
1495
+ );
1496
+ const cartographicCenter = C.Rectangle.center(rectangle);
1497
+ const canvas = scene.canvas;
1498
+ const aspectRatio = canvas.width / canvas.height;
1499
+ const earthRadius = 6371e3;
1500
+ const rectWidth = rectangle.width * earthRadius * Math.cos(cartographicCenter.latitude);
1501
+ const rectHeight = rectangle.height * earthRadius;
1502
+ const frustum = camera.frustum;
1503
+ const fovy = frustum.fovy || frustum.fov || C.Math.toRadians(60);
1504
+ const fovx = 2 * Math.atan(Math.tan(fovy / 2) * aspectRatio);
1505
+ const altitudeForHeight = rectHeight / 2 / Math.tan(fovy / 2);
1506
+ const altitudeForWidth = rectWidth / 2 / Math.tan(fovx / 2);
1507
+ const relativeAltitude = Math.max(altitudeForHeight, altitudeForWidth, 500) * heightMultiplier;
1508
+ const maxWaypointAlt = bounds.maxAlt ?? 0;
1509
+ const finalAltitude = maxWaypointAlt + relativeAltitude;
1510
+ console.log("[CameraManager] \u822A\u70B9\u6700\u5927\u6D77\u62D4:", maxWaypointAlt, "\u76F8\u5BF9\u9AD8\u5EA6:", relativeAltitude, "\u6700\u7EC8\u76F8\u673A\u9AD8\u5EA6:", finalAltitude);
1511
+ const destination = C.Cartesian3.fromRadians(
1512
+ cartographicCenter.longitude,
1513
+ cartographicCenter.latitude,
1514
+ finalAltitude
1515
+ );
1516
+ const orientation = topDown ? { heading: 0, pitch: C.Math.toRadians(-90), roll: 0 } : { heading: 0, pitch: C.Math.toRadians(-45), roll: 0 };
1517
+ if (duration > 0) {
1518
+ camera.flyTo({ destination, orientation, duration });
1519
+ } else {
1520
+ camera.setView({ destination, orientation });
1521
+ }
1522
+ return finalAltitude;
1523
+ }
1524
+ /**
1525
+ * 对当前 Cesium 场景进行截图
1526
+ *
1527
+ * @param options 截图选项
1528
+ * @returns 截图结果,包含 dataUrl、blob、file
1529
+ *
1530
+ * @example
1531
+ * ```typescript
1532
+ * const result = cameraManager.captureScreenshot();
1533
+ * console.log(result.dataUrl); // base64 图片
1534
+ * ```
1535
+ */
1536
+ captureScreenshot(options) {
1537
+ const { format = "image/png", quality = 0.92, fileName } = options || {};
1538
+ if (!this.viewer?.canvas) {
1539
+ throw new Error("\u65E0\u6548\u7684 Cesium Viewer \u5B9E\u4F8B\u6216 canvas \u4E0D\u5B58\u5728");
1540
+ }
1541
+ this.viewer.render();
1542
+ const canvas = this.viewer.canvas;
1543
+ const dataUrl = canvas.toDataURL(format, quality);
1544
+ const blob = this.dataURLtoBlob(dataUrl);
1545
+ const finalFileName = fileName || this.generateScreenshotFileName(format);
1546
+ const file = new File([blob], finalFileName, { type: format });
1547
+ return { dataUrl, blob, file };
1548
+ }
1549
+ /**
1550
+ * 调整视角并截图(异步)
1551
+ * 根据航点自动调整相机位置,等待渲染完成后截图
1552
+ *
1553
+ * @param options 截图选项
1554
+ * @returns 截图结果
1555
+ *
1556
+ * @example
1557
+ * ```typescript
1558
+ * const waypoints = [
1559
+ * { lon: 104.0, lat: 30.5 },
1560
+ * { lon: 104.1, lat: 30.6 },
1561
+ * ];
1562
+ * const result = await cameraManager.captureScreenshotWithView({
1563
+ * waypoints,
1564
+ * topDown: true,
1565
+ * padding: 0.3,
1566
+ * });
1567
+ * ```
1568
+ */
1569
+ async captureScreenshotWithView(options) {
1570
+ const {
1571
+ format = "image/png",
1572
+ quality = 0.92,
1573
+ fileName,
1574
+ waypoints,
1575
+ topDown = true,
1576
+ padding = 0.3,
1577
+ restoreCamera = true,
1578
+ renderDelay = 2e3
1579
+ } = options || {};
1580
+ if (!this.viewer?.scene) {
1581
+ throw new Error("\u65E0\u6548\u7684 Cesium Viewer \u5B9E\u4F8B");
1582
+ }
1583
+ const scene = this.viewer.scene;
1584
+ const camera = this.viewer.camera;
1585
+ const originalPosition = camera.position.clone();
1586
+ const originalHeading = camera.heading;
1587
+ const originalPitch = camera.pitch;
1588
+ const originalRoll = camera.roll;
1589
+ try {
1590
+ if (waypoints && waypoints.length > 0) {
1591
+ console.log("[CameraManager] \u5F00\u59CB\u8C03\u6574\u76F8\u673A\u4F4D\u7F6E\uFF0C\u822A\u70B9\u6570\u91CF:", waypoints.length);
1592
+ const altitude = this.flyToBoundsForScreenshot(waypoints, { padding, topDown, duration: 0 });
1593
+ console.log("[CameraManager] \u8BA1\u7B97\u7684\u76F8\u673A\u9AD8\u5EA6:", altitude);
1594
+ scene.requestRender();
1595
+ await this.waitForAnimationFrame();
1596
+ console.log("[CameraManager] \u7B49\u5F85\u74E6\u7247\u52A0\u8F7D (" + renderDelay + "ms)...");
1597
+ await this.delay(renderDelay);
1598
+ for (let i = 0; i < 20; i++) {
1599
+ scene.requestRender();
1600
+ await this.waitForAnimationFrame();
1601
+ if (i % 5 === 4) {
1602
+ await this.delay(100);
1603
+ }
1604
+ }
1605
+ console.log("[CameraManager] \u7B49\u5F85\u5B8C\u6210\uFF0C\u51C6\u5907\u622A\u56FE");
1606
+ }
1607
+ console.log("[CameraManager] \u5F00\u59CB\u622A\u56FE...");
1608
+ const result = await this.captureAfterRender({ format, quality, fileName });
1609
+ console.log("[CameraManager] \u622A\u56FE\u5B8C\u6210");
1610
+ if (restoreCamera && waypoints && waypoints.length > 0) {
1611
+ camera.setView({
1612
+ destination: originalPosition,
1613
+ orientation: {
1614
+ heading: originalHeading,
1615
+ pitch: originalPitch,
1616
+ roll: originalRoll
1617
+ }
1618
+ });
1619
+ }
1620
+ return result;
1621
+ } catch (error) {
1622
+ console.error("[CameraManager] \u622A\u56FE\u5931\u8D25:", error);
1623
+ if (restoreCamera) {
1624
+ camera.setView({
1625
+ destination: originalPosition,
1626
+ orientation: {
1627
+ heading: originalHeading,
1628
+ pitch: originalPitch,
1629
+ roll: originalRoll
1630
+ }
1631
+ });
1632
+ }
1633
+ throw error;
1634
+ }
1635
+ }
1636
+ /**
1637
+ * 等待渲染完成后截图
1638
+ */
1639
+ captureAfterRender(options) {
1640
+ return new Promise((resolve, reject) => {
1641
+ const { format = "image/png", quality = 0.92, fileName } = options;
1642
+ const scene = this.viewer.scene;
1643
+ const removeCallback = scene.postRender.addEventListener(() => {
1644
+ removeCallback();
1645
+ try {
1646
+ const canvas = scene.canvas;
1647
+ const dataUrl = canvas.toDataURL(format, quality);
1648
+ const blob = this.dataURLtoBlob(dataUrl);
1649
+ const finalFileName = fileName || this.generateScreenshotFileName(format);
1650
+ const file = new File([blob], finalFileName, { type: format });
1651
+ resolve({ dataUrl, blob, file });
1652
+ } catch (error) {
1653
+ reject(error);
1654
+ }
1655
+ });
1656
+ scene.requestRender();
1657
+ });
1658
+ }
1659
+ /**
1660
+ * 将 Base64 DataURL 转换为 Blob
1661
+ */
1662
+ dataURLtoBlob(dataUrl) {
1663
+ const arr = dataUrl.split(",");
1664
+ const mimeMatch = arr[0].match(/:(.*?);/);
1665
+ const mime = mimeMatch ? mimeMatch[1] : "image/png";
1666
+ const bstr = atob(arr[1]);
1667
+ let n = bstr.length;
1668
+ const u8arr = new Uint8Array(n);
1669
+ while (n--) {
1670
+ u8arr[n] = bstr.charCodeAt(n);
1671
+ }
1672
+ return new Blob([u8arr], { type: mime });
1673
+ }
1674
+ /**
1675
+ * 生成截图文件名
1676
+ */
1677
+ generateScreenshotFileName(format) {
1678
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
1679
+ const ext = format === "image/jpeg" ? "jpg" : format === "image/webp" ? "webp" : "png";
1680
+ return `screenshot_${timestamp}.${ext}`;
1681
+ }
1682
+ /**
1683
+ * 延迟工具函数
1684
+ */
1685
+ delay(ms) {
1686
+ return new Promise((resolve) => setTimeout(resolve, ms));
1687
+ }
1688
+ /**
1689
+ * 等待下一个动画帧
1690
+ */
1691
+ waitForAnimationFrame() {
1692
+ return new Promise((resolve) => requestAnimationFrame(() => resolve()));
1693
+ }
1096
1694
  };
1097
1695
 
1098
1696
  // src/core/SceneManager.ts
@@ -3577,6 +4175,28 @@ var VertexLabelManager = class {
3577
4175
  }
3578
4176
  if (this.labels[index]) {
3579
4177
  this.layer.entities.remove(this.labels[index]);
4178
+ this.labels[index] = void 0;
4179
+ }
4180
+ try {
4181
+ const entitiesToRemove = [];
4182
+ const allEntities = this.layer.entities.values;
4183
+ for (let i = 0; i < allEntities.length; i++) {
4184
+ const entity = allEntities[i];
4185
+ try {
4186
+ const props = entity.properties;
4187
+ const type = props?._type?.getValue?.() ?? props?._type;
4188
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4189
+ const vertexIndex = props?._vertexIndex?.getValue?.() ?? props?._vertexIndex;
4190
+ if (type === "vertex-label" && ownerId === this.ownerId && vertexIndex === index) {
4191
+ entitiesToRemove.push(entity);
4192
+ }
4193
+ } catch {
4194
+ }
4195
+ }
4196
+ for (const entity of entitiesToRemove) {
4197
+ this.layer.entities.remove(entity);
4198
+ }
4199
+ } catch {
3580
4200
  }
3581
4201
  this.labels[index] = this.createLabel(position, index, state);
3582
4202
  } catch (e) {
@@ -3594,8 +4214,8 @@ var VertexLabelManager = class {
3594
4214
  return;
3595
4215
  }
3596
4216
  const label = this.labels[index];
3597
- if (label && label.position) {
3598
- label.position.setValue(position);
4217
+ if (label) {
4218
+ label.position = position;
3599
4219
  }
3600
4220
  } catch (e) {
3601
4221
  console.error("Failed to update vertex label position:", e);
@@ -3603,12 +4223,11 @@ var VertexLabelManager = class {
3603
4223
  }
3604
4224
  /**
3605
4225
  * 插入新标签(在指定索引处)
4226
+ * 注意:此方法只更新内部数组,调用方需要随后调用 recreateAllLabels() 来重建所有标签
3606
4227
  */
3607
4228
  insertLabel(insertAt, position, state = "normal") {
3608
4229
  try {
3609
- const newLabel = this.createLabel(position, insertAt, state);
3610
- this.labels.splice(insertAt, 0, newLabel);
3611
- this.recreateAllLabels();
4230
+ this.labels.splice(insertAt, 0, void 0);
3612
4231
  } catch (e) {
3613
4232
  console.error("Failed to insert vertex label:", e);
3614
4233
  }
@@ -3638,6 +4257,27 @@ var VertexLabelManager = class {
3638
4257
  this.layer.entities.remove(this.labels[i]);
3639
4258
  }
3640
4259
  }
4260
+ try {
4261
+ const entitiesToRemove = [];
4262
+ const allEntities = this.layer.entities.values;
4263
+ for (let i = 0; i < allEntities.length; i++) {
4264
+ const entity = allEntities[i];
4265
+ try {
4266
+ const props = entity.properties;
4267
+ const type = props?._type?.getValue?.() ?? props?._type;
4268
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4269
+ if (type === "vertex-label" && ownerId === this.ownerId) {
4270
+ entitiesToRemove.push(entity);
4271
+ }
4272
+ } catch {
4273
+ }
4274
+ }
4275
+ for (const entity of entitiesToRemove) {
4276
+ this.layer.entities.remove(entity);
4277
+ }
4278
+ } catch {
4279
+ }
4280
+ this.labels = [];
3641
4281
  if (positions) {
3642
4282
  for (let i = 0; i < positions.length; i++) {
3643
4283
  const isEdited = editedIndices?.has(i) ?? false;
@@ -3666,6 +4306,26 @@ var VertexLabelManager = class {
3666
4306
  this.layer.entities.remove(label);
3667
4307
  }
3668
4308
  });
4309
+ try {
4310
+ const entitiesToRemove = [];
4311
+ const allEntities = this.layer.entities.values;
4312
+ for (let i = 0; i < allEntities.length; i++) {
4313
+ const entity = allEntities[i];
4314
+ try {
4315
+ const props = entity.properties;
4316
+ const type = props?._type?.getValue?.() ?? props?._type;
4317
+ const ownerId = props?._ownerId?.getValue?.() ?? props?._ownerId;
4318
+ if (type === "vertex-label" && ownerId === this.ownerId) {
4319
+ entitiesToRemove.push(entity);
4320
+ }
4321
+ } catch {
4322
+ }
4323
+ }
4324
+ for (const entity of entitiesToRemove) {
4325
+ this.layer.entities.remove(entity);
4326
+ }
4327
+ } catch {
4328
+ }
3669
4329
  this.labels = [];
3670
4330
  } catch (e) {
3671
4331
  console.error("Failed to destroy vertex labels:", e);
@@ -4296,7 +4956,7 @@ var VertexDragHandler = class {
4296
4956
  }
4297
4957
  };
4298
4958
 
4299
- // src/core/path-manager/utils/TerrainHeightQuery.ts
4959
+ // src/utils/TerrainHeightQuery.ts
4300
4960
  async function queryTerrainHeights(CesiumNS, viewer, positions) {
4301
4961
  const C = CesiumNS;
4302
4962
  if (!positions || positions.length === 0) {
@@ -4348,6 +5008,41 @@ async function queryTerrainHeightAsync(CesiumNS, viewer, position) {
4348
5008
  const heights = await queryTerrainHeights(CesiumNS, viewer, [position]);
4349
5009
  return heights[0] ?? 0;
4350
5010
  }
5011
+ async function queryTerrainHeightByLonLat(CesiumNS, viewer, lon, lat) {
5012
+ const C = CesiumNS;
5013
+ const position = C.Cartesian3.fromDegrees(lon, lat, 0);
5014
+ return queryTerrainHeightAsync(CesiumNS, viewer, position);
5015
+ }
5016
+ async function queryTerrainHeightsByLonLat(CesiumNS, viewer, coordinates) {
5017
+ const C = CesiumNS;
5018
+ if (!coordinates || coordinates.length === 0) {
5019
+ return [];
5020
+ }
5021
+ const positions = coordinates.map((coord) => {
5022
+ if (Array.isArray(coord)) {
5023
+ return C.Cartesian3.fromDegrees(coord[0], coord[1], 0);
5024
+ } else {
5025
+ return C.Cartesian3.fromDegrees(coord.lon, coord.lat, 0);
5026
+ }
5027
+ });
5028
+ return queryTerrainHeights(CesiumNS, viewer, positions);
5029
+ }
5030
+ function queryTerrainHeightByLonLatSync(CesiumNS, viewer, lon, lat) {
5031
+ const C = CesiumNS;
5032
+ try {
5033
+ const cartographic = C.Cartographic.fromDegrees(lon, lat);
5034
+ const globe = viewer.scene.globe;
5035
+ if (globe && typeof globe.getHeight === "function") {
5036
+ const height = globe.getHeight(cartographic);
5037
+ if (typeof height === "number" && !isNaN(height)) {
5038
+ return height;
5039
+ }
5040
+ }
5041
+ } catch (error) {
5042
+ console.warn("[TerrainHeightQuery] \u540C\u6B65\u67E5\u8BE2\u5931\u8D25:", error);
5043
+ }
5044
+ return 0;
5045
+ }
4351
5046
  function calculateAbsoluteHeight(altitudeMode, defaultAltitude, startPointAltitude, terrainHeight) {
4352
5047
  switch (altitudeMode) {
4353
5048
  case "absolute":
@@ -6225,15 +6920,15 @@ function startPathEditing(CesiumNS, viewer, entityOrId, options) {
6225
6920
  } catch {
6226
6921
  }
6227
6922
  }
6228
- try {
6229
- vertexLabelManager.updateLabelPosition(i, newPosition);
6230
- } catch {
6231
- }
6232
6923
  try {
6233
6924
  createOrUpdateMarkerForIndex(i);
6234
6925
  } catch {
6235
6926
  }
6236
6927
  }
6928
+ try {
6929
+ vertexLabelManager.recreateAllLabels(positions, editedIndices, activeIndex);
6930
+ } catch {
6931
+ }
6237
6932
  currentAltitudeMode = newMode;
6238
6933
  quickEditOptions.altitudeMode = newMode;
6239
6934
  try {
@@ -11805,6 +12500,6 @@ var PathSafetyChecker = class {
11805
12500
  var placeholder = { ready: true };
11806
12501
  var droneModelUrl = wurenji_default;
11807
12502
 
11808
- export { CZMLManager, CameraEventBus, CameraFOVController, CameraManager, Emitter, FlightSimulator, FrustumPyramid, LayerManager, PathSafetyChecker, PointCloudPicker, PolygonEditor, SceneManager, Selector, StateManager, assertCesiumAssetsConfigured, calculateAbsoluteHeight, calculateRelativeHeight, configureCesiumAssets, configureCesiumIonToken, convertPathToSinofly, convertSinoflyWayline, convertSinoflyWaylines, droneModelUrl, ensureCesiumIonToken, getCesiumBaseUrl, getCesiumIonToken, globalCameraEventBus, globalState, placeholder, queryTerrainHeightAsync, queryTerrainHeightSync, queryTerrainHeights, renderFlightPath, renderFlightPathPreview, toggle2D3D, version, versionInfo };
12503
+ export { CZMLManager, CameraEventBus, CameraFOVController, CameraManager, Emitter, FlightSimulator, FrustumPyramid, LayerManager, PathSafetyChecker, PointCloudPicker, PolygonEditor, SceneManager, Selector, StateManager, assertCesiumAssetsConfigured, calculateAbsoluteHeight, calculateBoundsDiagonal, calculateGeoBounds, calculateRelativeHeight, configureCesiumAssets, configureCesiumIonToken, convertPathToSinofly, convertSinoflyWayline, convertSinoflyWaylines, droneModelUrl, ensureCesiumIonToken, expandBounds, getCesiumBaseUrl, getCesiumIonToken, globalCameraEventBus, globalState, isPointInBounds, mergeBounds, placeholder, queryTerrainHeightAsync, queryTerrainHeightByLonLat, queryTerrainHeightByLonLatSync, queryTerrainHeightSync, queryTerrainHeights, queryTerrainHeightsByLonLat, renderFlightPath, renderFlightPathPreview, toggle2D3D, version, versionInfo };
11809
12504
  //# sourceMappingURL=index.js.map
11810
12505
  //# sourceMappingURL=index.js.map