@jorgmoritz/gis-manager 0.1.49 → 0.1.51

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.48"};
14
+ version: "0.1.51"};
15
15
 
16
16
  // src/utils/version.ts
17
17
  var version = package_default.version;
@@ -1693,6 +1693,765 @@ var CameraManager = class {
1693
1693
  }
1694
1694
  };
1695
1695
 
1696
+ // src/core/terrain-manager/TerrainManager.ts
1697
+ var TerrainManager = class {
1698
+ constructor(CesiumNS, viewer) {
1699
+ this.CesiumNS = CesiumNS;
1700
+ this.viewer = viewer;
1701
+ /** 已注册的地形源 */
1702
+ __publicField(this, "sources", /* @__PURE__ */ new Map());
1703
+ /** 当前激活的地形源 ID */
1704
+ __publicField(this, "activeSourceId", null);
1705
+ /** 事件发射器 */
1706
+ __publicField(this, "events", new Emitter());
1707
+ }
1708
+ /**
1709
+ * 注册地形源
1710
+ * @param config 地形源配置
1711
+ */
1712
+ register(config) {
1713
+ if (this.sources.has(config.id)) {
1714
+ console.warn(`[TerrainManager] \u5730\u5F62\u6E90 "${config.id}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u8986\u76D6`);
1715
+ }
1716
+ const normalizedConfig = {
1717
+ requestVertexNormals: true,
1718
+ requestWaterMask: true,
1719
+ priority: 0,
1720
+ ...config
1721
+ };
1722
+ this.sources.set(config.id, normalizedConfig);
1723
+ this.events.emit({ registered: { config: normalizedConfig } });
1724
+ console.log(`[TerrainManager] \u5DF2\u6CE8\u518C\u5730\u5F62\u6E90: ${config.id}`);
1725
+ }
1726
+ /**
1727
+ * 批量注册地形源
1728
+ * @param configs 地形源配置数组
1729
+ */
1730
+ registerBatch(configs) {
1731
+ configs.forEach((config) => this.register(config));
1732
+ }
1733
+ /**
1734
+ * 切换到指定地形源
1735
+ * @param sourceId 地形源 ID
1736
+ * @param options 切换选项
1737
+ */
1738
+ async switchTo(sourceId, options) {
1739
+ const config = this.sources.get(sourceId);
1740
+ if (!config) {
1741
+ const error = `\u5730\u5F62\u6E90 "${sourceId}" \u672A\u6CE8\u518C`;
1742
+ console.error(`[TerrainManager] ${error}`);
1743
+ return { success: false, activeId: this.activeSourceId, error };
1744
+ }
1745
+ try {
1746
+ const provider = await this.createTerrainProvider(config);
1747
+ if (!provider) {
1748
+ throw new Error("\u65E0\u6CD5\u521B\u5EFA\u5730\u5F62\u63D0\u4F9B\u8005");
1749
+ }
1750
+ const previousId = this.activeSourceId;
1751
+ this.viewer.terrainProvider = provider;
1752
+ this.activeSourceId = sourceId;
1753
+ this.events.emit({ switched: { fromId: previousId, toId: sourceId } });
1754
+ console.log(`[TerrainManager] \u5DF2\u5207\u6362\u5730\u5F62: ${previousId} -> ${sourceId}`);
1755
+ if (options?.flyTo && config.bounds) {
1756
+ await this.flyToBounds(config.bounds, options.flyDuration);
1757
+ }
1758
+ return { success: true, activeId: sourceId };
1759
+ } catch (error) {
1760
+ const errMsg = error instanceof Error ? error.message : String(error);
1761
+ console.error(`[TerrainManager] \u5207\u6362\u5730\u5F62\u5931\u8D25:`, error);
1762
+ this.events.emit({ error: { id: sourceId, error } });
1763
+ return { success: false, activeId: this.activeSourceId, error: errMsg };
1764
+ }
1765
+ }
1766
+ /**
1767
+ * 获取当前激活的地形源配置
1768
+ */
1769
+ getActive() {
1770
+ if (!this.activeSourceId) return null;
1771
+ return this.sources.get(this.activeSourceId) ?? null;
1772
+ }
1773
+ /**
1774
+ * 获取当前激活的地形源 ID
1775
+ */
1776
+ getActiveId() {
1777
+ return this.activeSourceId;
1778
+ }
1779
+ /**
1780
+ * 获取指定地形源配置
1781
+ * @param sourceId 地形源 ID
1782
+ */
1783
+ get(sourceId) {
1784
+ return this.sources.get(sourceId);
1785
+ }
1786
+ /**
1787
+ * 获取所有已注册的地形源
1788
+ */
1789
+ list() {
1790
+ return Array.from(this.sources.values());
1791
+ }
1792
+ /**
1793
+ * 检查地形源是否已注册
1794
+ * @param sourceId 地形源 ID
1795
+ */
1796
+ has(sourceId) {
1797
+ return this.sources.has(sourceId);
1798
+ }
1799
+ /**
1800
+ * 移除地形源
1801
+ * @param sourceId 地形源 ID
1802
+ */
1803
+ remove(sourceId) {
1804
+ if (!this.sources.has(sourceId)) {
1805
+ return false;
1806
+ }
1807
+ if (this.activeSourceId === sourceId) {
1808
+ this.reset();
1809
+ }
1810
+ this.sources.delete(sourceId);
1811
+ this.events.emit({ removed: { id: sourceId } });
1812
+ console.log(`[TerrainManager] \u5DF2\u79FB\u9664\u5730\u5F62\u6E90: ${sourceId}`);
1813
+ return true;
1814
+ }
1815
+ /**
1816
+ * 重置为椭球体(无地形)
1817
+ */
1818
+ reset() {
1819
+ const C = this.CesiumNS;
1820
+ const previousId = this.activeSourceId;
1821
+ this.viewer.terrainProvider = new C.EllipsoidTerrainProvider();
1822
+ this.activeSourceId = null;
1823
+ this.events.emit({ switched: { fromId: previousId, toId: null } });
1824
+ console.log(`[TerrainManager] \u5DF2\u91CD\u7F6E\u5730\u5F62\u4E3A\u692D\u7403\u4F53`);
1825
+ }
1826
+ /**
1827
+ * 清除所有已注册的地形源
1828
+ */
1829
+ clear() {
1830
+ this.reset();
1831
+ this.sources.clear();
1832
+ console.log(`[TerrainManager] \u5DF2\u6E05\u9664\u6240\u6709\u5730\u5F62\u6E90`);
1833
+ }
1834
+ /**
1835
+ * 销毁管理器
1836
+ */
1837
+ destroy() {
1838
+ this.clear();
1839
+ }
1840
+ /**
1841
+ * 创建地形提供者
1842
+ */
1843
+ async createTerrainProvider(config) {
1844
+ const C = this.CesiumNS;
1845
+ switch (config.providerType) {
1846
+ case "ellipsoid":
1847
+ return new C.EllipsoidTerrainProvider();
1848
+ case "cesium-ion": {
1849
+ const assetId = config.ionAssetId ?? 1;
1850
+ const IonResource = C.IonResource ?? C.Resource;
1851
+ if (IonResource?.fromAssetId) {
1852
+ const resource = await IonResource.fromAssetId(assetId);
1853
+ return await C.CesiumTerrainProvider.fromUrl(resource, {
1854
+ requestVertexNormals: config.requestVertexNormals,
1855
+ requestWaterMask: config.requestWaterMask
1856
+ });
1857
+ }
1858
+ if (C.createWorldTerrain) {
1859
+ return C.createWorldTerrain({
1860
+ requestVertexNormals: config.requestVertexNormals,
1861
+ requestWaterMask: config.requestWaterMask
1862
+ });
1863
+ }
1864
+ return void 0;
1865
+ }
1866
+ case "url": {
1867
+ if (!config.url) {
1868
+ throw new Error("URL \u7C7B\u578B\u5730\u5F62\u6E90\u5FC5\u987B\u63D0\u4F9B url");
1869
+ }
1870
+ return await C.CesiumTerrainProvider.fromUrl(config.url, {
1871
+ requestVertexNormals: config.requestVertexNormals,
1872
+ requestWaterMask: config.requestWaterMask
1873
+ });
1874
+ }
1875
+ default:
1876
+ return void 0;
1877
+ }
1878
+ }
1879
+ /**
1880
+ * 飞行到指定范围
1881
+ */
1882
+ async flyToBounds(bounds, duration = 2) {
1883
+ const C = this.CesiumNS;
1884
+ const [west, south, east, north] = bounds;
1885
+ const centerLon = (west + east) / 2;
1886
+ const centerLat = (south + north) / 2;
1887
+ const lonSpan = Math.abs(east - west);
1888
+ const latSpan = Math.abs(north - south);
1889
+ const maxSpan = Math.max(lonSpan, latSpan);
1890
+ const cameraHeight = Math.max(maxSpan * 111e3 * 1.5, 1e3);
1891
+ let terrainHeight = 0;
1892
+ try {
1893
+ const terrainProvider = this.viewer.terrainProvider;
1894
+ if (terrainProvider && !(terrainProvider instanceof C.EllipsoidTerrainProvider)) {
1895
+ const positions = [C.Cartographic.fromDegrees(centerLon, centerLat)];
1896
+ const sampledPositions = await C.sampleTerrainMostDetailed(terrainProvider, positions);
1897
+ terrainHeight = sampledPositions[0]?.height || 0;
1898
+ }
1899
+ } catch {
1900
+ }
1901
+ this.viewer.camera.flyTo({
1902
+ destination: C.Cartesian3.fromDegrees(centerLon, centerLat, terrainHeight + cameraHeight),
1903
+ orientation: {
1904
+ heading: C.Math.toRadians(0),
1905
+ pitch: C.Math.toRadians(-45),
1906
+ roll: 0
1907
+ },
1908
+ duration
1909
+ });
1910
+ }
1911
+ };
1912
+
1913
+ // src/core/imagery-manager/ImageryManager.ts
1914
+ var ImageryManager = class {
1915
+ constructor(CesiumNS, viewer) {
1916
+ this.CesiumNS = CesiumNS;
1917
+ this.viewer = viewer;
1918
+ /** 已加载的图层 */
1919
+ __publicField(this, "layers", /* @__PURE__ */ new Map());
1920
+ /** 事件发射器 */
1921
+ __publicField(this, "events", new Emitter());
1922
+ }
1923
+ /**
1924
+ * 添加影像图层
1925
+ * @param config 图层配置
1926
+ * @param options 加载选项
1927
+ */
1928
+ async add(config, options) {
1929
+ if (this.layers.has(config.id)) {
1930
+ console.warn(`[ImageryManager] \u56FE\u5C42 "${config.id}" \u5DF2\u5B58\u5728\uFF0C\u5C06\u88AB\u66FF\u6362`);
1931
+ this.remove(config.id);
1932
+ }
1933
+ try {
1934
+ const provider = this.createImageryProvider(config);
1935
+ if (!provider) {
1936
+ throw new Error("\u65E0\u6CD5\u521B\u5EFA\u5F71\u50CF\u63D0\u4F9B\u8005");
1937
+ }
1938
+ const layer = this.viewer.imageryLayers.addImageryProvider(provider);
1939
+ layer.alpha = config.opacity ?? 1;
1940
+ layer.show = config.show ?? true;
1941
+ layer._customId = config.id;
1942
+ this.layers.set(config.id, { config, layer });
1943
+ this.applyZIndex(config.id, config.zIndex ?? 0);
1944
+ this.events.emit({ added: { config } });
1945
+ console.log(`[ImageryManager] \u5DF2\u6DFB\u52A0\u56FE\u5C42: ${config.id}`);
1946
+ if (options?.flyTo && config.bounds) {
1947
+ await this.flyToBounds(config.bounds, options.flyDuration);
1948
+ }
1949
+ return { id: config.id, success: true };
1950
+ } catch (error) {
1951
+ const errMsg = error instanceof Error ? error.message : String(error);
1952
+ console.error(`[ImageryManager] \u6DFB\u52A0\u56FE\u5C42\u5931\u8D25:`, error);
1953
+ this.events.emit({ error: { id: config.id, error } });
1954
+ return { id: config.id, success: false, error: errMsg };
1955
+ }
1956
+ }
1957
+ /**
1958
+ * 批量添加影像图层
1959
+ * @param configs 图层配置数组
1960
+ * @param options 加载选项
1961
+ */
1962
+ async addBatch(configs, options) {
1963
+ const sorted = [...configs].sort((a, b) => (a.zIndex ?? 0) - (b.zIndex ?? 0));
1964
+ const results = [];
1965
+ for (const config of sorted) {
1966
+ const result = await this.add(config, options);
1967
+ results.push(result);
1968
+ }
1969
+ return results;
1970
+ }
1971
+ /**
1972
+ * 移除影像图层
1973
+ * @param layerId 图层 ID
1974
+ */
1975
+ remove(layerId) {
1976
+ const entry = this.layers.get(layerId);
1977
+ if (!entry) {
1978
+ return false;
1979
+ }
1980
+ this.viewer.imageryLayers.remove(entry.layer, true);
1981
+ this.layers.delete(layerId);
1982
+ this.events.emit({ removed: { id: layerId } });
1983
+ console.log(`[ImageryManager] \u5DF2\u79FB\u9664\u56FE\u5C42: ${layerId}`);
1984
+ return true;
1985
+ }
1986
+ /**
1987
+ * 设置图层可见性
1988
+ * @param layerId 图层 ID
1989
+ * @param visible 是否可见
1990
+ */
1991
+ setVisible(layerId, visible) {
1992
+ const entry = this.layers.get(layerId);
1993
+ if (!entry) {
1994
+ return false;
1995
+ }
1996
+ entry.layer.show = visible;
1997
+ entry.config.show = visible;
1998
+ this.events.emit({ visibilityChanged: { id: layerId, visible } });
1999
+ return true;
2000
+ }
2001
+ /**
2002
+ * 设置图层透明度
2003
+ * @param layerId 图层 ID
2004
+ * @param opacity 透明度 0-1
2005
+ */
2006
+ setOpacity(layerId, opacity) {
2007
+ const entry = this.layers.get(layerId);
2008
+ if (!entry) {
2009
+ return false;
2010
+ }
2011
+ const clampedOpacity = Math.max(0, Math.min(1, opacity));
2012
+ entry.layer.alpha = clampedOpacity;
2013
+ entry.config.opacity = clampedOpacity;
2014
+ this.events.emit({ opacityChanged: { id: layerId, opacity: clampedOpacity } });
2015
+ return true;
2016
+ }
2017
+ /**
2018
+ * 调整图层顺序
2019
+ * @param layerId 图层 ID
2020
+ * @param zIndex 新的层级
2021
+ */
2022
+ reorder(layerId, zIndex) {
2023
+ const entry = this.layers.get(layerId);
2024
+ if (!entry) {
2025
+ return false;
2026
+ }
2027
+ entry.config.zIndex = zIndex;
2028
+ this.applyZIndex(layerId, zIndex);
2029
+ this.events.emit({ orderChanged: { id: layerId, zIndex } });
2030
+ return true;
2031
+ }
2032
+ /**
2033
+ * 将图层移到最上层
2034
+ * @param layerId 图层 ID
2035
+ */
2036
+ bringToTop(layerId) {
2037
+ const entry = this.layers.get(layerId);
2038
+ if (!entry) {
2039
+ return false;
2040
+ }
2041
+ const imageryLayers = this.viewer.imageryLayers;
2042
+ const currentIndex = imageryLayers.indexOf(entry.layer);
2043
+ if (currentIndex >= 0 && currentIndex < imageryLayers.length - 1) {
2044
+ for (let i = currentIndex; i < imageryLayers.length - 1; i++) {
2045
+ imageryLayers.raise(entry.layer);
2046
+ }
2047
+ }
2048
+ const maxZIndex = Math.max(...Array.from(this.layers.values()).map((e) => e.config.zIndex ?? 0));
2049
+ entry.config.zIndex = maxZIndex + 1;
2050
+ this.events.emit({ orderChanged: { id: layerId, zIndex: entry.config.zIndex } });
2051
+ return true;
2052
+ }
2053
+ /**
2054
+ * 将图层移到最下层
2055
+ * @param layerId 图层 ID
2056
+ */
2057
+ sendToBottom(layerId) {
2058
+ const entry = this.layers.get(layerId);
2059
+ if (!entry) {
2060
+ return false;
2061
+ }
2062
+ const imageryLayers = this.viewer.imageryLayers;
2063
+ const currentIndex = imageryLayers.indexOf(entry.layer);
2064
+ if (currentIndex > 0) {
2065
+ for (let i = currentIndex; i > 0; i--) {
2066
+ imageryLayers.lower(entry.layer);
2067
+ }
2068
+ }
2069
+ const minZIndex = Math.min(...Array.from(this.layers.values()).map((e) => e.config.zIndex ?? 0));
2070
+ entry.config.zIndex = minZIndex - 1;
2071
+ this.events.emit({ orderChanged: { id: layerId, zIndex: entry.config.zIndex } });
2072
+ return true;
2073
+ }
2074
+ /**
2075
+ * 获取图层配置
2076
+ * @param layerId 图层 ID
2077
+ */
2078
+ get(layerId) {
2079
+ return this.layers.get(layerId)?.config;
2080
+ }
2081
+ /**
2082
+ * 获取底层 Cesium ImageryLayer
2083
+ * @param layerId 图层 ID
2084
+ */
2085
+ getLayer(layerId) {
2086
+ return this.layers.get(layerId)?.layer;
2087
+ }
2088
+ /**
2089
+ * 获取所有图层配置
2090
+ */
2091
+ list() {
2092
+ return Array.from(this.layers.values()).map((e) => e.config);
2093
+ }
2094
+ /**
2095
+ * 获取所有底图图层
2096
+ */
2097
+ listBaseLayers() {
2098
+ return this.list().filter((c) => c.layerType === "base");
2099
+ }
2100
+ /**
2101
+ * 获取所有叠加图层
2102
+ */
2103
+ listOverlays() {
2104
+ return this.list().filter((c) => c.layerType === "overlay");
2105
+ }
2106
+ /**
2107
+ * 检查图层是否存在
2108
+ * @param layerId 图层 ID
2109
+ */
2110
+ has(layerId) {
2111
+ return this.layers.has(layerId);
2112
+ }
2113
+ /**
2114
+ * 清除所有叠加层(保留底图)
2115
+ */
2116
+ clearOverlays() {
2117
+ const overlays = this.listOverlays();
2118
+ overlays.forEach((config) => this.remove(config.id));
2119
+ console.log(`[ImageryManager] \u5DF2\u6E05\u9664 ${overlays.length} \u4E2A\u53E0\u52A0\u5C42`);
2120
+ }
2121
+ /**
2122
+ * 清除所有图层
2123
+ */
2124
+ clear() {
2125
+ const ids = Array.from(this.layers.keys());
2126
+ ids.forEach((id) => this.remove(id));
2127
+ console.log(`[ImageryManager] \u5DF2\u6E05\u9664\u6240\u6709\u56FE\u5C42`);
2128
+ }
2129
+ /**
2130
+ * 销毁管理器
2131
+ */
2132
+ destroy() {
2133
+ this.clear();
2134
+ }
2135
+ /**
2136
+ * 创建影像提供者
2137
+ */
2138
+ createImageryProvider(config) {
2139
+ const C = this.CesiumNS;
2140
+ const autoDetectedConfig = this.autoDetectTiandituWmts(config);
2141
+ let rectangle;
2142
+ if (autoDetectedConfig.bounds) {
2143
+ let bounds = autoDetectedConfig.bounds;
2144
+ if (Math.abs(bounds[1]) > 90 && Math.abs(bounds[2]) <= 90) {
2145
+ bounds = [bounds[0], bounds[2], bounds[1], bounds[3]];
2146
+ }
2147
+ rectangle = C.Rectangle.fromDegrees(bounds[0], bounds[1], bounds[2], bounds[3]);
2148
+ }
2149
+ const minLevel = autoDetectedConfig.zoomRange?.min;
2150
+ const maxLevel = autoDetectedConfig.zoomRange?.max;
2151
+ switch (autoDetectedConfig.provider) {
2152
+ case "urlTemplate":
2153
+ case "tms":
2154
+ return new C.UrlTemplateImageryProvider({
2155
+ url: autoDetectedConfig.url,
2156
+ minimumLevel: minLevel,
2157
+ maximumLevel: maxLevel,
2158
+ rectangle,
2159
+ subdomains: autoDetectedConfig.subdomains
2160
+ });
2161
+ case "wmts":
2162
+ if (!autoDetectedConfig.wmts) {
2163
+ throw new Error("WMTS \u63D0\u4F9B\u8005\u9700\u8981 wmts \u914D\u7F6E");
2164
+ }
2165
+ return new C.WebMapTileServiceImageryProvider({
2166
+ url: autoDetectedConfig.url,
2167
+ layer: autoDetectedConfig.wmts.layer,
2168
+ style: autoDetectedConfig.wmts.style ?? "default",
2169
+ tileMatrixSetID: autoDetectedConfig.wmts.tileMatrixSetID,
2170
+ format: autoDetectedConfig.wmts.format ?? "image/png",
2171
+ subdomains: autoDetectedConfig.subdomains,
2172
+ tilingScheme: new C.WebMercatorTilingScheme(),
2173
+ maximumLevel: maxLevel
2174
+ });
2175
+ case "wms":
2176
+ if (!autoDetectedConfig.wms) {
2177
+ throw new Error("WMS \u63D0\u4F9B\u8005\u9700\u8981 wms \u914D\u7F6E");
2178
+ }
2179
+ return new C.WebMapServiceImageryProvider({
2180
+ url: autoDetectedConfig.url,
2181
+ layers: autoDetectedConfig.wms.layers,
2182
+ parameters: autoDetectedConfig.wms.parameters,
2183
+ rectangle
2184
+ });
2185
+ case "arcgis":
2186
+ return new C.ArcGisMapServerImageryProvider({
2187
+ url: autoDetectedConfig.url
2188
+ });
2189
+ default:
2190
+ return void 0;
2191
+ }
2192
+ }
2193
+ /**
2194
+ * 自动检测天地图 WMTS 服务并补充配置
2195
+ */
2196
+ autoDetectTiandituWmts(config) {
2197
+ const url = config.url;
2198
+ const isTiandituWmts = url.includes("tianditu.gov.cn") && url.includes("wmts");
2199
+ if (!isTiandituWmts) {
2200
+ return config;
2201
+ }
2202
+ if (config.provider === "wmts" && config.wmts) {
2203
+ return config;
2204
+ }
2205
+ const tk = url.match(/tk=([^&]+)/)?.[1] || "f44fcec420c3eb1c56656e9a22dabb17";
2206
+ let layer = "img";
2207
+ let baseUrl = url;
2208
+ if (url.includes("/img_w/")) {
2209
+ layer = "img";
2210
+ baseUrl = `https://t{s}.tianditu.gov.cn/img_w/wmts?tk=${tk}`;
2211
+ } else if (url.includes("/cia_w/")) {
2212
+ layer = "cia";
2213
+ baseUrl = `https://t{s}.tianditu.gov.cn/cia_w/wmts?tk=${tk}`;
2214
+ } else if (url.includes("/vec_w/")) {
2215
+ layer = "vec";
2216
+ baseUrl = `https://t{s}.tianditu.gov.cn/vec_w/wmts?tk=${tk}`;
2217
+ } else if (url.includes("/cva_w/")) {
2218
+ layer = "cva";
2219
+ baseUrl = `https://t{s}.tianditu.gov.cn/cva_w/wmts?tk=${tk}`;
2220
+ } else if (url.includes("/ter_w/")) {
2221
+ layer = "ter";
2222
+ baseUrl = `https://t{s}.tianditu.gov.cn/ter_w/wmts?tk=${tk}`;
2223
+ } else if (url.includes("/cta_w/")) {
2224
+ layer = "cta";
2225
+ baseUrl = `https://t{s}.tianditu.gov.cn/cta_w/wmts?tk=${tk}`;
2226
+ }
2227
+ console.log(`[ImageryManager] \u81EA\u52A8\u68C0\u6D4B\u5230\u5929\u5730\u56FE WMTS \u670D\u52A1: layer=${layer}`);
2228
+ return {
2229
+ ...config,
2230
+ url: baseUrl,
2231
+ provider: "wmts",
2232
+ subdomains: config.subdomains || ["0", "1", "2", "3", "4", "5", "6", "7"],
2233
+ wmts: {
2234
+ layer,
2235
+ style: "default",
2236
+ tileMatrixSetID: "w",
2237
+ format: "tiles"
2238
+ },
2239
+ zoomRange: config.zoomRange || { max: 18 }
2240
+ };
2241
+ }
2242
+ /**
2243
+ * 应用图层顺序
2244
+ */
2245
+ applyZIndex(layerId, zIndex) {
2246
+ const entry = this.layers.get(layerId);
2247
+ if (!entry) return;
2248
+ const imageryLayers = this.viewer.imageryLayers;
2249
+ const currentIndex = imageryLayers.indexOf(entry.layer);
2250
+ if (currentIndex < 0) return;
2251
+ const sortedEntries = Array.from(this.layers.entries()).map(([id, e]) => ({ id, zIndex: e.config.zIndex ?? 0, layer: e.layer })).sort((a, b) => a.zIndex - b.zIndex);
2252
+ const targetIndex = sortedEntries.findIndex((e) => e.id === layerId);
2253
+ if (targetIndex < 0) return;
2254
+ const diff = targetIndex - currentIndex;
2255
+ if (diff > 0) {
2256
+ for (let i = 0; i < diff; i++) {
2257
+ imageryLayers.raise(entry.layer);
2258
+ }
2259
+ } else if (diff < 0) {
2260
+ for (let i = 0; i < -diff; i++) {
2261
+ imageryLayers.lower(entry.layer);
2262
+ }
2263
+ }
2264
+ }
2265
+ /**
2266
+ * 飞行到指定范围
2267
+ */
2268
+ async flyToBounds(bounds, duration = 2) {
2269
+ const C = this.CesiumNS;
2270
+ const [west, south, east, north] = bounds;
2271
+ const centerLon = (west + east) / 2;
2272
+ const centerLat = (south + north) / 2;
2273
+ const lonSpan = Math.abs(east - west);
2274
+ const latSpan = Math.abs(north - south);
2275
+ const maxSpan = Math.max(lonSpan, latSpan);
2276
+ const cameraHeight = Math.max(maxSpan * 111e3 * 1.2, 500);
2277
+ this.viewer.camera.flyTo({
2278
+ destination: C.Cartesian3.fromDegrees(centerLon, centerLat, cameraHeight),
2279
+ orientation: {
2280
+ heading: C.Math.toRadians(0),
2281
+ pitch: C.Math.toRadians(-90),
2282
+ roll: 0
2283
+ },
2284
+ duration
2285
+ });
2286
+ }
2287
+ };
2288
+
2289
+ // src/core/tileset-manager/TilesetManager.ts
2290
+ var TilesetManager = class {
2291
+ constructor(CesiumNS, viewer) {
2292
+ this.CesiumNS = CesiumNS;
2293
+ this.viewer = viewer;
2294
+ /** 已加载的 Tileset */
2295
+ __publicField(this, "tilesets", /* @__PURE__ */ new Map());
2296
+ /** 事件发射器 */
2297
+ __publicField(this, "events", new Emitter());
2298
+ }
2299
+ /**
2300
+ * 添加 3D Tiles
2301
+ * @param config Tileset 配置
2302
+ * @param options 加载选项
2303
+ */
2304
+ async add(config, options) {
2305
+ if (this.tilesets.has(config.id)) {
2306
+ console.log(`[TilesetManager] Tileset "${config.id}" \u5DF2\u5B58\u5728\uFF0C\u8FD4\u56DE\u7F13\u5B58`);
2307
+ return { id: config.id, success: true };
2308
+ }
2309
+ try {
2310
+ const tileset = await this.createTileset(config);
2311
+ if (!tileset) {
2312
+ throw new Error("\u65E0\u6CD5\u521B\u5EFA Tileset");
2313
+ }
2314
+ this.viewer.scene.primitives.add(tileset);
2315
+ tileset._customId = config.id;
2316
+ tileset._url = config.url;
2317
+ this.tilesets.set(config.id, { config, tileset });
2318
+ this.events.emit({ added: { config } });
2319
+ console.log(`[TilesetManager] \u5DF2\u6DFB\u52A0 Tileset: ${config.id}`);
2320
+ if (options?.flyTo) {
2321
+ await this.flyToTileset(tileset, options.flyDuration);
2322
+ }
2323
+ return { id: config.id, success: true };
2324
+ } catch (error) {
2325
+ const errMsg = error instanceof Error ? error.message : String(error);
2326
+ console.error(`[TilesetManager] \u6DFB\u52A0 Tileset \u5931\u8D25:`, error);
2327
+ this.events.emit({ error: { id: config.id, error } });
2328
+ return { id: config.id, success: false, error: errMsg };
2329
+ }
2330
+ }
2331
+ /**
2332
+ * 移除 Tileset
2333
+ * @param tilesetId Tileset ID
2334
+ */
2335
+ remove(tilesetId) {
2336
+ const entry = this.tilesets.get(tilesetId);
2337
+ if (!entry) {
2338
+ return false;
2339
+ }
2340
+ this.viewer.scene.primitives.remove(entry.tileset);
2341
+ this.tilesets.delete(tilesetId);
2342
+ this.events.emit({ removed: { id: tilesetId } });
2343
+ console.log(`[TilesetManager] \u5DF2\u79FB\u9664 Tileset: ${tilesetId}`);
2344
+ return true;
2345
+ }
2346
+ /**
2347
+ * 设置 Tileset 可见性
2348
+ * @param tilesetId Tileset ID
2349
+ * @param visible 是否可见
2350
+ */
2351
+ setVisible(tilesetId, visible) {
2352
+ const entry = this.tilesets.get(tilesetId);
2353
+ if (!entry) {
2354
+ return false;
2355
+ }
2356
+ entry.tileset.show = visible;
2357
+ entry.config.show = visible;
2358
+ this.events.emit({ visibilityChanged: { id: tilesetId, visible } });
2359
+ return true;
2360
+ }
2361
+ /**
2362
+ * 获取 Tileset 配置
2363
+ * @param tilesetId Tileset ID
2364
+ */
2365
+ get(tilesetId) {
2366
+ return this.tilesets.get(tilesetId)?.config;
2367
+ }
2368
+ /**
2369
+ * 获取底层 Cesium Tileset
2370
+ * @param tilesetId Tileset ID
2371
+ */
2372
+ getTileset(tilesetId) {
2373
+ return this.tilesets.get(tilesetId)?.tileset;
2374
+ }
2375
+ /**
2376
+ * 获取所有 Tileset 配置
2377
+ */
2378
+ list() {
2379
+ return Array.from(this.tilesets.values()).map((e) => e.config);
2380
+ }
2381
+ /**
2382
+ * 检查 Tileset 是否存在
2383
+ * @param tilesetId Tileset ID
2384
+ */
2385
+ has(tilesetId) {
2386
+ return this.tilesets.has(tilesetId);
2387
+ }
2388
+ /**
2389
+ * 飞行到指定 Tileset
2390
+ * @param tilesetId Tileset ID
2391
+ * @param duration 飞行时长
2392
+ */
2393
+ async flyTo(tilesetId, duration) {
2394
+ const entry = this.tilesets.get(tilesetId);
2395
+ if (!entry) {
2396
+ return false;
2397
+ }
2398
+ await this.flyToTileset(entry.tileset, duration);
2399
+ return true;
2400
+ }
2401
+ /**
2402
+ * 清除所有 Tileset
2403
+ */
2404
+ clear() {
2405
+ const ids = Array.from(this.tilesets.keys());
2406
+ ids.forEach((id) => this.remove(id));
2407
+ console.log(`[TilesetManager] \u5DF2\u6E05\u9664\u6240\u6709 Tileset`);
2408
+ }
2409
+ /**
2410
+ * 销毁管理器
2411
+ */
2412
+ destroy() {
2413
+ this.clear();
2414
+ }
2415
+ /**
2416
+ * 创建 Tileset
2417
+ */
2418
+ async createTileset(config) {
2419
+ const C = this.CesiumNS;
2420
+ const pointCloudShading = config.pointCloudShading ?? {
2421
+ attenuation: true,
2422
+ geometricErrorScale: 1,
2423
+ eyeDomeLighting: true,
2424
+ eyeDomeLightingStrength: 1,
2425
+ eyeDomeLightingRadius: 1
2426
+ };
2427
+ const tileset = await C.Cesium3DTileset.fromUrl(config.url, {
2428
+ show: config.show ?? true,
2429
+ maximumScreenSpaceError: config.maximumScreenSpaceError ?? 16,
2430
+ dynamicScreenSpaceError: true,
2431
+ dynamicScreenSpaceErrorDensity: 278e-5,
2432
+ dynamicScreenSpaceErrorFactor: 4,
2433
+ skipLevelOfDetail: false,
2434
+ baseScreenSpaceError: 1024,
2435
+ skipScreenSpaceErrorFactor: 16,
2436
+ skipLevels: 1,
2437
+ immediatelyLoadDesiredLevelOfDetail: false,
2438
+ loadSiblings: false,
2439
+ cullWithChildrenBounds: true,
2440
+ pointCloudShading
2441
+ });
2442
+ return tileset;
2443
+ }
2444
+ /**
2445
+ * 飞行到 Tileset
2446
+ */
2447
+ async flyToTileset(tileset, duration = 2) {
2448
+ try {
2449
+ await this.viewer.flyTo(tileset, { duration });
2450
+ } catch {
2451
+ }
2452
+ }
2453
+ };
2454
+
1696
2455
  // src/core/SceneManager.ts
1697
2456
  var SceneManager = class {
1698
2457
  constructor(CesiumNS, options) {
@@ -1700,6 +2459,9 @@ var SceneManager = class {
1700
2459
  __publicField(this, "viewer");
1701
2460
  __publicField(this, "layerManager");
1702
2461
  __publicField(this, "cameraManager");
2462
+ __publicField(this, "terrainManager");
2463
+ __publicField(this, "imageryManager");
2464
+ __publicField(this, "tilesetManager");
1703
2465
  console.log("SceneManager constructor", options);
1704
2466
  assertCesiumAssetsConfigured();
1705
2467
  ensureCesiumIonToken(CesiumNS);
@@ -1707,10 +2469,8 @@ var SceneManager = class {
1707
2469
  const container = this.resolveContainer(options.container);
1708
2470
  this.viewer = new CesiumNS.Viewer(container, {
1709
2471
  baseLayer: false,
1710
- // 时间控制相关
1711
2472
  animation: false,
1712
2473
  timeline: false,
1713
- // UI控件
1714
2474
  geocoder: false,
1715
2475
  homeButton: false,
1716
2476
  sceneModePicker: false,
@@ -1718,15 +2478,11 @@ var SceneManager = class {
1718
2478
  navigationHelpButton: false,
1719
2479
  fullscreenButton: false,
1720
2480
  vrButton: false,
1721
- // 信息框
1722
2481
  infoBox: false,
1723
2482
  selectionIndicator: false,
1724
- // 默认数据源
1725
2483
  shouldAnimate: false,
1726
- // 地形和影像
1727
2484
  terrainProvider: void 0,
1728
2485
  creditContainer: void 0,
1729
- // 其他
1730
2486
  contextOptions: {
1731
2487
  webgl: {
1732
2488
  alpha: true,
@@ -1735,7 +2491,6 @@ var SceneManager = class {
1735
2491
  antialias: true,
1736
2492
  powerPreference: "high-performance",
1737
2493
  preserveDrawingBuffer: true
1738
- // 必需:允许 canvas.toDataURL() 截图
1739
2494
  }
1740
2495
  },
1741
2496
  ...viewerOptions
@@ -1747,6 +2502,9 @@ var SceneManager = class {
1747
2502
  }
1748
2503
  });
1749
2504
  this.cameraManager = new CameraManager(CesiumNS, this.viewer);
2505
+ this.terrainManager = new TerrainManager(CesiumNS, this.viewer);
2506
+ this.imageryManager = new ImageryManager(CesiumNS, this.viewer);
2507
+ this.tilesetManager = new TilesetManager(CesiumNS, this.viewer);
1750
2508
  if (imagery) this.applyImagery(imagery);
1751
2509
  if (terrain) this.applyTerrain(terrain);
1752
2510
  if (typeof shadows === "boolean") this.setShadows(shadows);
@@ -1757,7 +2515,7 @@ var SceneManager = class {
1757
2515
  return this.viewer;
1758
2516
  }
1759
2517
  /**
1760
- * Set a black background for areas without imagery. Optionally clear existing base imagery layers.
2518
+ * Set a black background for areas without imagery.
1761
2519
  */
1762
2520
  useBlackBackground(removeAllImagery = true) {
1763
2521
  const C = this.CesiumNS;
@@ -1765,6 +2523,9 @@ var SceneManager = class {
1765
2523
  this.viewer.scene.globe.baseColor = C.Color.BLACK;
1766
2524
  }
1767
2525
  destroy() {
2526
+ this.terrainManager?.destroy();
2527
+ this.imageryManager?.destroy();
2528
+ this.tilesetManager?.destroy();
1768
2529
  this.viewer?.destroy();
1769
2530
  this.viewer = void 0;
1770
2531
  }
@@ -1789,6 +2550,18 @@ var SceneManager = class {
1789
2550
  getLayerManager() {
1790
2551
  return this.layerManager;
1791
2552
  }
2553
+ getTerrainManager() {
2554
+ return this.terrainManager;
2555
+ }
2556
+ getImageryManager() {
2557
+ return this.imageryManager;
2558
+ }
2559
+ getTilesetManager() {
2560
+ return this.tilesetManager;
2561
+ }
2562
+ /**
2563
+ * @deprecated 请使用 getImageryManager().add() 代替
2564
+ */
1792
2565
  setBaseImagery(url, layer_id, opts) {
1793
2566
  for (let i = 0; i < this.viewer.imageryLayers.length; i++) {
1794
2567
  const layer2 = this.viewer.imageryLayers.get(i);
@@ -1811,7 +2584,9 @@ var SceneManager = class {
1811
2584
  }
1812
2585
  return layer;
1813
2586
  }
1814
- // private tilese3DtMap: Map<string, any> = new Map();
2587
+ /**
2588
+ * @deprecated 请使用 getTilesetManager().add() 代替
2589
+ */
1815
2590
  async set3DTiles(url, layer_id) {
1816
2591
  const primitives = this.viewer.scene.primitives;
1817
2592
  for (let i = primitives.length - 1; i >= 0; i--) {
@@ -1835,6 +2610,9 @@ var SceneManager = class {
1835
2610
  return void 0;
1836
2611
  }
1837
2612
  }
2613
+ /**
2614
+ * @deprecated 请使用 getTerrainManager().switchTo() 代替
2615
+ */
1838
2616
  async setTerrain(options) {
1839
2617
  if (this.viewer.scene.skyBox) {
1840
2618
  this.viewer.scene.skyBox.show = false;
@@ -1843,7 +2621,6 @@ var SceneManager = class {
1843
2621
  this.viewer.scene.globe.baseColor = this.CesiumNS.Color.BLACK;
1844
2622
  return this.applyTerrain(options);
1845
2623
  }
1846
- // Expose DSM (3D Tiles) loading for demos and consumers
1847
2624
  async addDSM(options) {
1848
2625
  const handle = await this.layerManager.addDSM(options);
1849
2626
  try {
@@ -1872,8 +2649,7 @@ var SceneManager = class {
1872
2649
  try {
1873
2650
  const handle = this.layerManager.addDOM(options);
1874
2651
  if (!handle) return void 0;
1875
- const layer = this.layerManager.getImageryLayer(handle.id);
1876
- return layer;
2652
+ return this.layerManager.getImageryLayer(handle.id);
1877
2653
  } catch (e) {
1878
2654
  console.error("[SceneManager] Failed to apply imagery:", e);
1879
2655
  return void 0;
@@ -1882,7 +2658,6 @@ var SceneManager = class {
1882
2658
  async apply3Dtiles(url) {
1883
2659
  const tileset = await this.CesiumNS.Cesium3DTileset.fromUrl(url, {
1884
2660
  maximumScreenSpaceError: 16,
1885
- // 降低以提高点云质量
1886
2661
  dynamicScreenSpaceError: true,
1887
2662
  dynamicScreenSpaceErrorDensity: 278e-5,
1888
2663
  dynamicScreenSpaceErrorFactor: 4,
@@ -1893,14 +2668,12 @@ var SceneManager = class {
1893
2668
  immediatelyLoadDesiredLevelOfDetail: false,
1894
2669
  loadSiblings: false,
1895
2670
  cullWithChildrenBounds: true,
1896
- // 点云特定选项
1897
2671
  pointCloudShading: {
1898
2672
  attenuation: true,
1899
2673
  geometricErrorScale: 1,
1900
2674
  maximumAttenuation: void 0,
1901
2675
  baseResolution: void 0,
1902
2676
  eyeDomeLighting: true,
1903
- // 启用 EDL 可以改善点云视觉效果
1904
2677
  eyeDomeLightingStrength: 1,
1905
2678
  eyeDomeLightingRadius: 1
1906
2679
  }
@@ -1910,7 +2683,6 @@ var SceneManager = class {
1910
2683
  async applyTerrain(options) {
1911
2684
  try {
1912
2685
  const handle = await this.layerManager.addDEM(options);
1913
- console.log("handle", this.viewer.terrainProvider);
1914
2686
  return handle ? this.viewer.terrainProvider : void 0;
1915
2687
  } catch (e) {
1916
2688
  console.error("[SceneManager] Failed to apply terrain:", e);
@@ -13335,6 +14107,7 @@ var MassPolygonManager = class {
13335
14107
  // 事件处理器
13336
14108
  __publicField(this, "hoverHandler");
13337
14109
  __publicField(this, "clickHandler");
14110
+ __publicField(this, "rightClickHandler");
13338
14111
  // 悬停状态
13339
14112
  __publicField(this, "highlightedId");
13340
14113
  __publicField(this, "originalHighlightFillColor");
@@ -13343,11 +14116,36 @@ var MassPolygonManager = class {
13343
14116
  __publicField(this, "selectedId");
13344
14117
  __publicField(this, "originalSelectFillColor");
13345
14118
  __publicField(this, "originalSelectOutlineColor");
14119
+ // 贴地模式高亮用的独立 Primitive
14120
+ __publicField(this, "highlightPrimitive");
14121
+ __publicField(this, "selectPrimitive");
14122
+ // 贴地模式轮廓线
14123
+ __publicField(this, "groundOutlinePrimitive");
13346
14124
  // 节流相关
13347
14125
  __publicField(this, "lastPickTime", 0);
13348
14126
  __publicField(this, "pickThrottleMs", 50);
13349
14127
  // 悬停标签
13350
14128
  __publicField(this, "hoverLabel");
14129
+ // 静态标签(每个多边形的名称标签)
14130
+ __publicField(this, "staticLabelCollection");
14131
+ __publicField(this, "staticLabelsVisible", false);
14132
+ __publicField(this, "staticLabelStyle", {
14133
+ font: "12px sans-serif",
14134
+ fillColor: "#000000",
14135
+ // 黑色字
14136
+ outlineColor: "#ffffff",
14137
+ // 白色描边
14138
+ outlineWidth: 2,
14139
+ showBackground: false,
14140
+ backgroundColor: "rgba(0,0,0,0.5)",
14141
+ backgroundPadding: [4, 2],
14142
+ pixelOffset: [0, 0],
14143
+ scale: 1
14144
+ });
14145
+ // 地形高度缓存(用于 hover 标签等需要快速访问地形高度的场景)
14146
+ __publicField(this, "terrainHeightCache", /* @__PURE__ */ new Map());
14147
+ // 销毁标志
14148
+ __publicField(this, "isDestroyed", false);
13351
14149
  // ========== 编辑支持方法 ==========
13352
14150
  // 隐藏的多边形原始颜色存储
13353
14151
  __publicField(this, "hiddenPolygonColors", /* @__PURE__ */ new Map());
@@ -13361,6 +14159,7 @@ var MassPolygonManager = class {
13361
14159
  this.interactionOptions = {
13362
14160
  enableHover: false,
13363
14161
  enableClick: false,
14162
+ enableRightClick: false,
13364
14163
  highlightStyle: {
13365
14164
  fillColor: "rgba(255, 255, 0, 0.7)",
13366
14165
  outlineColor: "rgba(255, 255, 0, 1)"
@@ -13380,7 +14179,8 @@ var MassPolygonManager = class {
13380
14179
  pixelOffset: [0, -20]
13381
14180
  },
13382
14181
  onHover: void 0,
13383
- onClick: void 0
14182
+ onClick: void 0,
14183
+ onRightClick: void 0
13384
14184
  };
13385
14185
  }
13386
14186
  /**
@@ -13412,6 +14212,7 @@ var MassPolygonManager = class {
13412
14212
  this.interactionOptions = {
13413
14213
  enableHover: interaction.enableHover ?? false,
13414
14214
  enableClick: interaction.enableClick ?? false,
14215
+ enableRightClick: interaction.enableRightClick ?? false,
13415
14216
  highlightStyle: {
13416
14217
  fillColor: interaction.highlightStyle?.fillColor ?? this.interactionOptions.highlightStyle.fillColor,
13417
14218
  outlineColor: interaction.highlightStyle?.outlineColor ?? this.interactionOptions.highlightStyle.outlineColor
@@ -13431,12 +14232,27 @@ var MassPolygonManager = class {
13431
14232
  pixelOffset: interaction.hoverLabelStyle?.pixelOffset ?? this.interactionOptions.hoverLabelStyle.pixelOffset
13432
14233
  },
13433
14234
  onHover: interaction.onHover,
13434
- onClick: interaction.onClick
14235
+ onClick: interaction.onClick,
14236
+ onRightClick: interaction.onRightClick
13435
14237
  };
13436
14238
  }
13437
14239
  this.isClampToGround = options?.clampToGround ?? false;
13438
14240
  const asynchronous = options?.asynchronous ?? true;
13439
14241
  this.polygonHeight = options?.height ?? 0;
14242
+ if (options?.staticLabelStyle) {
14243
+ const labelStyle = options.staticLabelStyle;
14244
+ this.staticLabelStyle = {
14245
+ font: labelStyle.font ?? this.staticLabelStyle.font,
14246
+ fillColor: labelStyle.fillColor ?? this.staticLabelStyle.fillColor,
14247
+ outlineColor: labelStyle.outlineColor ?? this.staticLabelStyle.outlineColor,
14248
+ outlineWidth: labelStyle.outlineWidth ?? this.staticLabelStyle.outlineWidth,
14249
+ showBackground: labelStyle.showBackground ?? this.staticLabelStyle.showBackground,
14250
+ backgroundColor: labelStyle.backgroundColor ?? this.staticLabelStyle.backgroundColor,
14251
+ backgroundPadding: labelStyle.backgroundPadding ?? this.staticLabelStyle.backgroundPadding,
14252
+ pixelOffset: labelStyle.pixelOffset ?? this.staticLabelStyle.pixelOffset,
14253
+ scale: labelStyle.scale ?? this.staticLabelStyle.scale
14254
+ };
14255
+ }
13440
14256
  if (this.isClampToGround && this.viewer.scene.globe) {
13441
14257
  this.viewer.scene.globe.depthTestAgainstTerrain = true;
13442
14258
  }
@@ -13456,55 +14272,112 @@ var MassPolygonManager = class {
13456
14272
  continue;
13457
14273
  }
13458
14274
  const positions = this.convertPointsToCartesian(polygon.points);
13459
- const polygonGeometry = new C.PolygonGeometry({
13460
- polygonHierarchy: new C.PolygonHierarchy(positions),
13461
- perPositionHeight: false,
13462
- height: this.polygonHeight
13463
- // 使用配置的高度
13464
- });
13465
- fillInstances.push(new C.GeometryInstance({
13466
- geometry: polygonGeometry,
13467
- id: polygon.id,
13468
- attributes: {
13469
- color: C.ColorGeometryInstanceAttribute.fromColor(fillColor),
13470
- show: new C.ShowGeometryInstanceAttribute(true)
13471
- }
13472
- }));
13473
- const outlinePositionsWithHeight = polygon.points.map(
13474
- (p) => C.Cartesian3.fromDegrees(p.lon, p.lat, this.polygonHeight)
14275
+ if (this.isClampToGround) {
14276
+ const polygonGeometry = new C.PolygonGeometry({
14277
+ polygonHierarchy: new C.PolygonHierarchy(positions)
14278
+ });
14279
+ fillInstances.push(new C.GeometryInstance({
14280
+ geometry: polygonGeometry,
14281
+ id: polygon.id,
14282
+ attributes: {
14283
+ color: C.ColorGeometryInstanceAttribute.fromColor(fillColor)
14284
+ }
14285
+ }));
14286
+ } else {
14287
+ const polygonGeometry = new C.PolygonGeometry({
14288
+ polygonHierarchy: new C.PolygonHierarchy(positions),
14289
+ perPositionHeight: false,
14290
+ height: this.polygonHeight
14291
+ });
14292
+ fillInstances.push(new C.GeometryInstance({
14293
+ geometry: polygonGeometry,
14294
+ id: polygon.id,
14295
+ attributes: {
14296
+ color: C.ColorGeometryInstanceAttribute.fromColor(fillColor),
14297
+ show: new C.ShowGeometryInstanceAttribute(true)
14298
+ }
14299
+ }));
14300
+ }
14301
+ const outlinePositions = polygon.points.map(
14302
+ (p) => C.Cartesian3.fromDegrees(p.lon, p.lat, this.isClampToGround ? 0 : this.polygonHeight)
13475
14303
  );
13476
- outlinePositionsWithHeight.push(outlinePositionsWithHeight[0]);
13477
- const outlineGeometry = new C.PolylineGeometry({
13478
- positions: outlinePositionsWithHeight,
13479
- width: this.currentStyle.outlineWidth
13480
- });
13481
- outlineInstances.push(new C.GeometryInstance({
13482
- geometry: outlineGeometry,
13483
- id: `${polygon.id}-outline`,
13484
- attributes: {
13485
- color: C.ColorGeometryInstanceAttribute.fromColor(outlineColor),
13486
- show: new C.ShowGeometryInstanceAttribute(true)
13487
- }
13488
- }));
14304
+ outlinePositions.push(outlinePositions[0]);
14305
+ if (this.isClampToGround) {
14306
+ const groundOutlineGeometry = new C.GroundPolylineGeometry({
14307
+ positions: outlinePositions,
14308
+ width: this.currentStyle.outlineWidth
14309
+ });
14310
+ outlineInstances.push(new C.GeometryInstance({
14311
+ geometry: groundOutlineGeometry,
14312
+ id: `${polygon.id}-outline`,
14313
+ attributes: {
14314
+ color: C.ColorGeometryInstanceAttribute.fromColor(outlineColor)
14315
+ }
14316
+ }));
14317
+ } else {
14318
+ const outlineGeometry = new C.PolylineGeometry({
14319
+ positions: outlinePositions,
14320
+ width: this.currentStyle.outlineWidth
14321
+ });
14322
+ outlineInstances.push(new C.GeometryInstance({
14323
+ geometry: outlineGeometry,
14324
+ id: `${polygon.id}-outline`,
14325
+ attributes: {
14326
+ color: C.ColorGeometryInstanceAttribute.fromColor(outlineColor),
14327
+ show: new C.ShowGeometryInstanceAttribute(true)
14328
+ }
14329
+ }));
14330
+ }
13489
14331
  }
13490
14332
  if (fillInstances.length > 0) {
13491
14333
  const appearance = new C.PerInstanceColorAppearance({ flat: true, translucent: true });
13492
- this.primitive = new C.Primitive({ geometryInstances: fillInstances, appearance, asynchronous });
14334
+ if (this.isClampToGround) {
14335
+ this.primitive = new C.GroundPrimitive({
14336
+ geometryInstances: fillInstances,
14337
+ appearance,
14338
+ asynchronous,
14339
+ releaseGeometryInstances: false,
14340
+ allowPicking: true
14341
+ });
14342
+ } else {
14343
+ this.primitive = new C.Primitive({
14344
+ geometryInstances: fillInstances,
14345
+ appearance,
14346
+ asynchronous,
14347
+ releaseGeometryInstances: false,
14348
+ allowPicking: true
14349
+ });
14350
+ }
13493
14351
  this.layerCollection.add(this.primitive);
13494
14352
  }
13495
14353
  if (outlineInstances.length > 0) {
13496
- const appearance = new C.PolylineColorAppearance();
13497
- this.outlinePrimitive = new C.Primitive({ geometryInstances: outlineInstances, appearance, asynchronous });
13498
- this.layerCollection.add(this.outlinePrimitive);
14354
+ if (this.isClampToGround) {
14355
+ const appearance = new C.PolylineColorAppearance();
14356
+ this.groundOutlinePrimitive = new C.GroundPolylinePrimitive({
14357
+ geometryInstances: outlineInstances,
14358
+ appearance,
14359
+ asynchronous
14360
+ });
14361
+ this.layerCollection.add(this.groundOutlinePrimitive);
14362
+ } else {
14363
+ const appearance = new C.PolylineColorAppearance();
14364
+ this.outlinePrimitive = new C.Primitive({ geometryInstances: outlineInstances, appearance, asynchronous });
14365
+ this.layerCollection.add(this.outlinePrimitive);
14366
+ }
13499
14367
  }
13500
- this.labelCollection = new C.LabelCollection();
14368
+ this.labelCollection = new C.LabelCollection({ scene: this.viewer.scene });
13501
14369
  this.layerCollection.add(this.labelCollection);
14370
+ this.staticLabelCollection = new C.LabelCollection({ scene: this.viewer.scene });
14371
+ this.layerCollection.add(this.staticLabelCollection);
13502
14372
  if (this.interactionOptions.enableHover) {
13503
14373
  this.setupHoverHandler();
13504
14374
  }
13505
14375
  if (this.interactionOptions.enableClick) {
13506
14376
  this.setupClickHandler();
13507
14377
  }
14378
+ if (this.interactionOptions.enableRightClick) {
14379
+ this.setupRightClickHandler();
14380
+ }
13508
14381
  this.viewer.scene.requestRender();
13509
14382
  return {
13510
14383
  primitive: this.primitive,
@@ -13525,19 +14398,25 @@ var MassPolygonManager = class {
13525
14398
  const now = Date.now();
13526
14399
  if (now - this.lastPickTime < this.pickThrottleMs) return;
13527
14400
  this.lastPickTime = now;
13528
- const pickedObject = this.viewer.scene.pick(movement.endPosition);
13529
- if (pickedObject?.id && typeof pickedObject.id === "string") {
13530
- const polygonId = pickedObject.id.endsWith("-outline") ? pickedObject.id.replace("-outline", "") : pickedObject.id;
13531
- if (this.polygonDataMap.has(polygonId)) {
13532
- if (this.highlightedId !== polygonId) {
13533
- this.clearHighlight();
13534
- this.highlightPolygon(polygonId);
13535
- const data = this.polygonDataMap.get(polygonId);
13536
- if (data) this.showLabel(data);
13537
- this.interactionOptions.onHover?.(polygonId, data ?? null);
14401
+ let polygonId = null;
14402
+ if (this.isClampToGround) {
14403
+ polygonId = this.pickPolygonByPosition(movement.endPosition);
14404
+ } else {
14405
+ const pickedObject = this.viewer.scene.pick(movement.endPosition);
14406
+ if (pickedObject?.id && typeof pickedObject.id === "string") {
14407
+ const pickedId = pickedObject.id.endsWith("-outline") ? pickedObject.id.replace("-outline", "") : pickedObject.id;
14408
+ if (this.polygonDataMap.has(pickedId)) {
14409
+ polygonId = pickedId;
13538
14410
  }
13539
- } else {
13540
- this.handleMouseLeave();
14411
+ }
14412
+ }
14413
+ if (polygonId) {
14414
+ if (this.highlightedId !== polygonId) {
14415
+ this.clearHighlight();
14416
+ this.highlightPolygon(polygonId);
14417
+ const data = this.polygonDataMap.get(polygonId);
14418
+ if (data) this.showLabel(data);
14419
+ this.interactionOptions.onHover?.(polygonId, data ?? null);
13541
14420
  }
13542
14421
  } else {
13543
14422
  this.handleMouseLeave();
@@ -13557,23 +14436,26 @@ var MassPolygonManager = class {
13557
14436
  this.clickHandler = new C.ScreenSpaceEventHandler(this.viewer.scene.canvas);
13558
14437
  this.clickHandler.setInputAction(
13559
14438
  (movement) => {
13560
- const pickedObject = this.viewer.scene.pick(movement.position);
13561
- if (pickedObject?.id && typeof pickedObject.id === "string") {
13562
- const polygonId = pickedObject.id.endsWith("-outline") ? pickedObject.id.replace("-outline", "") : pickedObject.id;
13563
- if (this.polygonDataMap.has(polygonId)) {
13564
- if (this.selectedId === polygonId) {
13565
- this.deselect();
13566
- this.interactionOptions.onClick?.(null, null);
13567
- } else {
13568
- this.select(polygonId);
13569
- const data = this.polygonDataMap.get(polygonId);
13570
- this.interactionOptions.onClick?.(polygonId, data ?? null);
14439
+ let polygonId = null;
14440
+ if (this.isClampToGround) {
14441
+ polygonId = this.pickPolygonByPosition(movement.position);
14442
+ } else {
14443
+ const pickedObject = this.viewer.scene.pick(movement.position);
14444
+ if (pickedObject?.id && typeof pickedObject.id === "string") {
14445
+ const pickedId = pickedObject.id.endsWith("-outline") ? pickedObject.id.replace("-outline", "") : pickedObject.id;
14446
+ if (this.polygonDataMap.has(pickedId)) {
14447
+ polygonId = pickedId;
13571
14448
  }
14449
+ }
14450
+ }
14451
+ if (polygonId) {
14452
+ if (this.selectedId === polygonId) {
14453
+ this.deselect();
14454
+ this.interactionOptions.onClick?.(null, null);
13572
14455
  } else {
13573
- if (this.selectedId) {
13574
- this.deselect();
13575
- this.interactionOptions.onClick?.(null, null);
13576
- }
14456
+ this.select(polygonId);
14457
+ const data = this.polygonDataMap.get(polygonId);
14458
+ this.interactionOptions.onClick?.(polygonId, data ?? null);
13577
14459
  }
13578
14460
  } else {
13579
14461
  if (this.selectedId) {
@@ -13585,6 +14467,79 @@ var MassPolygonManager = class {
13585
14467
  C.ScreenSpaceEventType.LEFT_CLICK
13586
14468
  );
13587
14469
  }
14470
+ /**
14471
+ * 设置右键事件处理器
14472
+ */
14473
+ setupRightClickHandler() {
14474
+ if (this.rightClickHandler) {
14475
+ this.rightClickHandler.destroy();
14476
+ }
14477
+ const C = this.CesiumNS;
14478
+ this.rightClickHandler = new C.ScreenSpaceEventHandler(this.viewer.scene.canvas);
14479
+ this.rightClickHandler.setInputAction(
14480
+ (movement) => {
14481
+ let polygonId = null;
14482
+ if (this.isClampToGround) {
14483
+ polygonId = this.pickPolygonByPosition(movement.position);
14484
+ } else {
14485
+ const pickedObject = this.viewer.scene.pick(movement.position);
14486
+ if (pickedObject?.id && typeof pickedObject.id === "string") {
14487
+ const pickedId = pickedObject.id.endsWith("-outline") ? pickedObject.id.replace("-outline", "") : pickedObject.id;
14488
+ if (this.polygonDataMap.has(pickedId)) {
14489
+ polygonId = pickedId;
14490
+ }
14491
+ }
14492
+ }
14493
+ if (polygonId) {
14494
+ this.select(polygonId);
14495
+ const data = this.polygonDataMap.get(polygonId);
14496
+ if (data) {
14497
+ const canvas = this.viewer.scene.canvas;
14498
+ const canvasRect = canvas.getBoundingClientRect();
14499
+ const screenX = canvasRect.left + movement.position.x;
14500
+ const screenY = canvasRect.top + movement.position.y;
14501
+ this.interactionOptions.onRightClick?.(polygonId, data, { x: screenX, y: screenY });
14502
+ }
14503
+ }
14504
+ },
14505
+ C.ScreenSpaceEventType.RIGHT_CLICK
14506
+ );
14507
+ }
14508
+ /**
14509
+ * 通过屏幕坐标获取地理位置,然后判断在哪个多边形内
14510
+ * 用于贴地模式下的精确拾取
14511
+ */
14512
+ pickPolygonByPosition(screenPosition) {
14513
+ const C = this.CesiumNS;
14514
+ const ray = this.viewer.camera.getPickRay(screenPosition);
14515
+ if (!ray) return null;
14516
+ const cartesian = this.viewer.scene.globe.pick(ray, this.viewer.scene);
14517
+ if (!cartesian) return null;
14518
+ const cartographic = C.Cartographic.fromCartesian(cartesian);
14519
+ const lon = C.Math.toDegrees(cartographic.longitude);
14520
+ const lat = C.Math.toDegrees(cartographic.latitude);
14521
+ for (const [id, polygon] of this.polygonDataMap) {
14522
+ if (this.isPointInPolygon(lon, lat, polygon.points)) {
14523
+ return id;
14524
+ }
14525
+ }
14526
+ return null;
14527
+ }
14528
+ /**
14529
+ * 判断点是否在多边形内(射线法)
14530
+ */
14531
+ isPointInPolygon(lon, lat, points) {
14532
+ let inside = false;
14533
+ const n = points.length;
14534
+ for (let i = 0, j = n - 1; i < n; j = i++) {
14535
+ const xi = points[i].lon, yi = points[i].lat;
14536
+ const xj = points[j].lon, yj = points[j].lat;
14537
+ if (yi > lat !== yj > lat && lon < (xj - xi) * (lat - yi) / (yj - yi) + xi) {
14538
+ inside = !inside;
14539
+ }
14540
+ }
14541
+ return inside;
14542
+ }
13588
14543
  /**
13589
14544
  * 处理鼠标离开
13590
14545
  */
@@ -13602,6 +14557,11 @@ var MassPolygonManager = class {
13602
14557
  if (id === this.selectedId) return;
13603
14558
  const C = this.CesiumNS;
13604
14559
  if (!this.primitive) return;
14560
+ if (this.isClampToGround) {
14561
+ this.highlightPolygonWithSeparatePrimitive(id, "highlight");
14562
+ this.highlightedId = id;
14563
+ return;
14564
+ }
13605
14565
  try {
13606
14566
  const fillAttributes = this.primitive.getGeometryInstanceAttributes?.(id);
13607
14567
  if (fillAttributes?.color) {
@@ -13622,11 +14582,60 @@ var MassPolygonManager = class {
13622
14582
  } catch (e) {
13623
14583
  }
13624
14584
  }
14585
+ /**
14586
+ * 贴地模式下使用独立 Primitive 高亮/选中多边形
14587
+ */
14588
+ highlightPolygonWithSeparatePrimitive(id, type) {
14589
+ const C = this.CesiumNS;
14590
+ const polygonData = this.polygonDataMap.get(id);
14591
+ if (!polygonData) return;
14592
+ if (type === "highlight" && this.highlightPrimitive) {
14593
+ this.layerCollection?.remove(this.highlightPrimitive);
14594
+ this.highlightPrimitive = void 0;
14595
+ } else if (type === "select" && this.selectPrimitive) {
14596
+ this.layerCollection?.remove(this.selectPrimitive);
14597
+ this.selectPrimitive = void 0;
14598
+ }
14599
+ const positions = this.convertPointsToCartesian(polygonData.points);
14600
+ const color = type === "highlight" ? C.Color.fromCssColorString(this.interactionOptions.highlightStyle.fillColor) : C.Color.fromCssColorString(this.interactionOptions.selectStyle.fillColor);
14601
+ const geometry = new C.PolygonGeometry({
14602
+ polygonHierarchy: new C.PolygonHierarchy(positions)
14603
+ });
14604
+ const instance = new C.GeometryInstance({
14605
+ geometry,
14606
+ id: `${id}-${type}`,
14607
+ attributes: {
14608
+ color: C.ColorGeometryInstanceAttribute.fromColor(color)
14609
+ }
14610
+ });
14611
+ const primitive = new C.GroundPrimitive({
14612
+ geometryInstances: instance,
14613
+ appearance: new C.PerInstanceColorAppearance({ flat: true, translucent: true }),
14614
+ asynchronous: false
14615
+ // 同步创建,立即显示
14616
+ });
14617
+ if (type === "highlight") {
14618
+ this.highlightPrimitive = primitive;
14619
+ } else {
14620
+ this.selectPrimitive = primitive;
14621
+ }
14622
+ this.layerCollection?.add(primitive);
14623
+ this.viewer.scene.requestRender();
14624
+ }
13625
14625
  /**
13626
14626
  * 清除悬停高亮
13627
14627
  */
13628
14628
  clearHighlight() {
13629
14629
  if (!this.highlightedId) return;
14630
+ if (this.isClampToGround) {
14631
+ if (this.highlightPrimitive) {
14632
+ this.layerCollection?.remove(this.highlightPrimitive);
14633
+ this.highlightPrimitive = void 0;
14634
+ }
14635
+ this.highlightedId = void 0;
14636
+ this.viewer.scene.requestRender();
14637
+ return;
14638
+ }
13630
14639
  try {
13631
14640
  if (this.primitive && this.originalHighlightFillColor) {
13632
14641
  const fillAttributes = this.primitive.getGeometryInstanceAttributes?.(this.highlightedId);
@@ -13662,6 +14671,11 @@ var MassPolygonManager = class {
13662
14671
  }
13663
14672
  const C = this.CesiumNS;
13664
14673
  if (!this.primitive) return false;
14674
+ if (this.isClampToGround) {
14675
+ this.highlightPolygonWithSeparatePrimitive(id, "select");
14676
+ this.selectedId = id;
14677
+ return true;
14678
+ }
13665
14679
  try {
13666
14680
  const fillAttributes = this.primitive.getGeometryInstanceAttributes?.(id);
13667
14681
  if (fillAttributes?.color) {
@@ -13689,6 +14703,15 @@ var MassPolygonManager = class {
13689
14703
  */
13690
14704
  deselect() {
13691
14705
  if (!this.selectedId) return;
14706
+ if (this.isClampToGround) {
14707
+ if (this.selectPrimitive) {
14708
+ this.layerCollection?.remove(this.selectPrimitive);
14709
+ this.selectPrimitive = void 0;
14710
+ }
14711
+ this.selectedId = void 0;
14712
+ this.viewer.scene.requestRender();
14713
+ return;
14714
+ }
13692
14715
  try {
13693
14716
  if (this.primitive && this.originalSelectFillColor) {
13694
14717
  const fillAttributes = this.primitive.getGeometryInstanceAttributes?.(this.selectedId);
@@ -13723,7 +14746,7 @@ var MassPolygonManager = class {
13723
14746
  return this.selectedId ?? null;
13724
14747
  }
13725
14748
  /**
13726
- * 计算多边形中心点
14749
+ * 计算多边形中心点(简单平均)
13727
14750
  */
13728
14751
  calculatePolygonCenter(points) {
13729
14752
  let sumLon = 0, sumLat = 0, sumHeight = 0;
@@ -13738,8 +14761,45 @@ var MassPolygonManager = class {
13738
14761
  height: sumHeight / points.length
13739
14762
  };
13740
14763
  }
14764
+ /**
14765
+ * 计算多边形质心(更准确的中心点算法)
14766
+ * 使用多边形质心公式,对于凹多边形也能得到正确的中心位置
14767
+ */
14768
+ calculatePolygonCentroid(points) {
14769
+ if (points.length < 3) {
14770
+ return this.calculatePolygonCenter(points);
14771
+ }
14772
+ let signedArea = 0;
14773
+ let cx = 0;
14774
+ let cy = 0;
14775
+ let sumHeight = 0;
14776
+ const n = points.length;
14777
+ for (let i = 0; i < n; i++) {
14778
+ const x0 = points[i].lon;
14779
+ const y0 = points[i].lat;
14780
+ const x1 = points[(i + 1) % n].lon;
14781
+ const y1 = points[(i + 1) % n].lat;
14782
+ const a = x0 * y1 - x1 * y0;
14783
+ signedArea += a;
14784
+ cx += (x0 + x1) * a;
14785
+ cy += (y0 + y1) * a;
14786
+ sumHeight += points[i].height ?? 0;
14787
+ }
14788
+ signedArea *= 0.5;
14789
+ if (Math.abs(signedArea) < 1e-10) {
14790
+ return this.calculatePolygonCenter(points);
14791
+ }
14792
+ cx = cx / (6 * signedArea);
14793
+ cy = cy / (6 * signedArea);
14794
+ return {
14795
+ lon: cx,
14796
+ lat: cy,
14797
+ height: sumHeight / n
14798
+ };
14799
+ }
13741
14800
  /**
13742
14801
  * 显示悬停标签
14802
+ * 使用缓存的地形高度(如果可用)来正确放置标签
13743
14803
  */
13744
14804
  showLabel(polygonData) {
13745
14805
  if (!this.interactionOptions.showHoverLabel || !polygonData.name) return;
@@ -13748,7 +14808,24 @@ var MassPolygonManager = class {
13748
14808
  const style = this.interactionOptions.hoverLabelStyle;
13749
14809
  this.hideLabel();
13750
14810
  const center = this.calculatePolygonCenter(polygonData.points);
13751
- const position = C.Cartesian3.fromDegrees(center.lon, center.lat, this.polygonHeight);
14811
+ let labelHeight;
14812
+ if (this.isClampToGround) {
14813
+ const cachedHeight = this.terrainHeightCache.get(polygonData.id);
14814
+ if (cachedHeight !== void 0) {
14815
+ labelHeight = cachedHeight + 2;
14816
+ } else {
14817
+ const terrainHeight = queryTerrainHeightByLonLatSync(
14818
+ this.CesiumNS,
14819
+ this.viewer,
14820
+ center.lon,
14821
+ center.lat
14822
+ );
14823
+ labelHeight = terrainHeight + 2;
14824
+ }
14825
+ } else {
14826
+ labelHeight = this.polygonHeight;
14827
+ }
14828
+ const position = C.Cartesian3.fromDegrees(center.lon, center.lat, labelHeight);
13752
14829
  this.hoverLabel = this.labelCollection.add({
13753
14830
  position,
13754
14831
  text: polygonData.name,
@@ -13809,36 +14886,170 @@ var MassPolygonManager = class {
13809
14886
  }
13810
14887
  /**
13811
14888
  * 飞行到所有多边形的范围
14889
+ *
14890
+ * 优化说明:
14891
+ * 1. 多点采样地形高度(四角 + 中心),取最大值确保相机不会陷入地下
14892
+ * 2. 修正 pitch 角度对视角高度的影响计算
14893
+ * 3. 添加最小/最大高度限制,防止极端情况
14894
+ * 4. 使用异步地形查询获取更精确的高度
14895
+ * 5. 添加销毁检查,防止异步回调在实例销毁后执行
13812
14896
  */
13813
14897
  flyToBounds(options) {
14898
+ if (this.isDestroyed) {
14899
+ console.warn("[MassPolygonManager] \u5B9E\u4F8B\u5DF2\u9500\u6BC1\uFF0C\u8DF3\u8FC7 flyToBounds");
14900
+ return;
14901
+ }
13814
14902
  const bounds = this.getBounds();
13815
14903
  if (!bounds) {
13816
14904
  console.warn("[MassPolygonManager] \u6CA1\u6709\u591A\u8FB9\u5F62\u6570\u636E\uFF0C\u65E0\u6CD5\u98DE\u884C");
13817
14905
  return;
13818
14906
  }
14907
+ if (bounds.width <= 0 || bounds.height <= 0) {
14908
+ console.warn("[MassPolygonManager] \u591A\u8FB9\u5F62\u8303\u56F4\u65E0\u6548\uFF0C\u8DF3\u8FC7\u98DE\u884C");
14909
+ return;
14910
+ }
14911
+ const C = this.CesiumNS;
14912
+ const camera = this.viewer.camera;
14913
+ const duration = options?.duration ?? 2;
14914
+ const padding = options?.padding ?? 0.2;
14915
+ const pitch = options?.pitch ?? C.Math.toRadians(-45);
14916
+ const heading = options?.heading ?? 0;
14917
+ const minHeight = options?.minHeight ?? 100;
14918
+ const maxHeight = options?.maxHeight ?? 1e5;
14919
+ const paddedWest = bounds.west - bounds.width * padding;
14920
+ const paddedEast = bounds.east + bounds.width * padding;
14921
+ const paddedSouth = bounds.south - bounds.height * padding;
14922
+ const paddedNorth = bounds.north + bounds.height * padding;
14923
+ const samplePoints = [
14924
+ [bounds.centerLon, bounds.centerLat],
14925
+ // 中心
14926
+ [paddedWest, paddedSouth],
14927
+ // 左下
14928
+ [paddedEast, paddedSouth],
14929
+ // 右下
14930
+ [paddedWest, paddedNorth],
14931
+ // 左上
14932
+ [paddedEast, paddedNorth],
14933
+ // 右上
14934
+ [(paddedWest + paddedEast) / 2, paddedSouth],
14935
+ // 下边中点
14936
+ [(paddedWest + paddedEast) / 2, paddedNorth],
14937
+ // 上边中点
14938
+ [paddedWest, (paddedSouth + paddedNorth) / 2],
14939
+ // 左边中点
14940
+ [paddedEast, (paddedSouth + paddedNorth) / 2]
14941
+ // 右边中点
14942
+ ];
14943
+ Promise.all(
14944
+ samplePoints.map(
14945
+ ([lon, lat]) => queryTerrainHeightByLonLat(this.CesiumNS, this.viewer, lon, lat)
14946
+ )
14947
+ ).then((terrainHeights) => {
14948
+ if (this.isDestroyed) {
14949
+ console.warn("[MassPolygonManager] \u5B9E\u4F8B\u5DF2\u9500\u6BC1\uFF0C\u8DF3\u8FC7\u98DE\u884C\u64CD\u4F5C");
14950
+ return;
14951
+ }
14952
+ const is2DMode = this.viewer.scene.mode === C.SceneMode.SCENE2D;
14953
+ const validTerrainHeights = terrainHeights.filter((h) => h >= -500 && h < 1e4);
14954
+ const maxTerrainHeight = is2DMode || validTerrainHeights.length === 0 ? 0 : Math.max(...validTerrainHeights, 0);
14955
+ const earthRadius = 6371e3;
14956
+ const avgLat = (paddedSouth + paddedNorth) / 2;
14957
+ const latRad = avgLat * (Math.PI / 180);
14958
+ const rectWidth = (paddedEast - paddedWest) * (Math.PI / 180) * earthRadius * Math.cos(latRad);
14959
+ const rectHeight = (paddedNorth - paddedSouth) * (Math.PI / 180) * earthRadius;
14960
+ if (!isFinite(rectWidth) || !isFinite(rectHeight) || rectWidth <= 0 || rectHeight <= 0) {
14961
+ console.warn("[MassPolygonManager] \u8303\u56F4\u8BA1\u7B97\u7ED3\u679C\u65E0\u6548\uFF0C\u8DF3\u8FC7\u98DE\u884C");
14962
+ return;
14963
+ }
14964
+ const frustum = camera.frustum;
14965
+ const fovy = frustum.fovy || frustum.fov || C.Math.toRadians(60);
14966
+ const canvas = this.viewer.scene.canvas;
14967
+ const aspectRatio = canvas.width / canvas.height;
14968
+ const absPitch = Math.abs(pitch);
14969
+ const sinPitch = Math.sin(absPitch);
14970
+ let viewHeight;
14971
+ if (absPitch > C.Math.toRadians(80)) {
14972
+ const tanHalfFovy = Math.tan(fovy / 2);
14973
+ const heightForVertical = rectHeight / (2 * tanHalfFovy);
14974
+ const heightForHorizontal = rectWidth / (2 * tanHalfFovy * aspectRatio);
14975
+ viewHeight = Math.max(heightForVertical, heightForHorizontal);
14976
+ } else {
14977
+ const tanHalfFovy = Math.tan(fovy / 2);
14978
+ const heightForVertical = sinPitch > 0.1 ? rectHeight / (2 * tanHalfFovy * sinPitch) : rectHeight / (2 * tanHalfFovy);
14979
+ const heightForHorizontal = rectWidth / (2 * tanHalfFovy * aspectRatio);
14980
+ viewHeight = Math.max(heightForVertical, heightForHorizontal);
14981
+ }
14982
+ viewHeight *= 1.2;
14983
+ viewHeight = Math.max(minHeight, Math.min(maxHeight, viewHeight));
14984
+ const altitude = maxTerrainHeight + viewHeight;
14985
+ const destination = C.Cartesian3.fromDegrees(bounds.centerLon, bounds.centerLat, altitude);
14986
+ const orientation = { heading, pitch, roll: 0 };
14987
+ if (duration > 0) {
14988
+ camera.flyTo({ destination, orientation, duration });
14989
+ } else {
14990
+ camera.setView({ destination, orientation });
14991
+ }
14992
+ console.log(
14993
+ `[MassPolygonManager] \u98DE\u884C\u5230\u8FB9\u754C: \u4E2D\u5FC3(${bounds.centerLon.toFixed(6)}, ${bounds.centerLat.toFixed(6)}), \u8303\u56F4(${rectWidth.toFixed(0)}m x ${rectHeight.toFixed(0)}m), \u6700\u9AD8\u5730\u5F62: ${maxTerrainHeight.toFixed(0)}m, \u89C6\u89D2\u9AD8\u5EA6: ${viewHeight.toFixed(0)}m, \u76F8\u673A\u9AD8\u5EA6: ${altitude.toFixed(0)}m`
14994
+ );
14995
+ }).catch((error) => {
14996
+ if (this.isDestroyed) {
14997
+ return;
14998
+ }
14999
+ console.warn("[MassPolygonManager] \u5730\u5F62\u67E5\u8BE2\u5931\u8D25\uFF0C\u4F7F\u7528\u540C\u6B65\u65B9\u6CD5:", error);
15000
+ this.flyToBoundsSync(options);
15001
+ });
15002
+ }
15003
+ /**
15004
+ * 同步版本的飞行方法(降级方案)
15005
+ */
15006
+ flyToBoundsSync(options) {
15007
+ const bounds = this.getBounds();
15008
+ if (!bounds) return;
13819
15009
  const C = this.CesiumNS;
13820
15010
  const camera = this.viewer.camera;
13821
15011
  const duration = options?.duration ?? 2;
13822
15012
  const padding = options?.padding ?? 0.2;
13823
15013
  const pitch = options?.pitch ?? C.Math.toRadians(-45);
13824
15014
  const heading = options?.heading ?? 0;
15015
+ const minHeight = options?.minHeight ?? 100;
15016
+ const maxHeight = options?.maxHeight ?? 1e5;
13825
15017
  const paddedWest = bounds.west - bounds.width * padding;
13826
15018
  const paddedEast = bounds.east + bounds.width * padding;
13827
15019
  const paddedSouth = bounds.south - bounds.height * padding;
13828
15020
  const paddedNorth = bounds.north + bounds.height * padding;
15021
+ const terrainHeight = queryTerrainHeightByLonLatSync(
15022
+ this.CesiumNS,
15023
+ this.viewer,
15024
+ bounds.centerLon,
15025
+ bounds.centerLat
15026
+ );
15027
+ const is2DMode = this.viewer.scene.mode === C.SceneMode.SCENE2D;
15028
+ const validTerrainHeight = is2DMode || terrainHeight < -500 || terrainHeight >= 1e4 ? 0 : terrainHeight;
13829
15029
  const earthRadius = 6371e3;
13830
- const rectWidth = (paddedEast - paddedWest) * (Math.PI / 180) * earthRadius * Math.cos(bounds.centerLat * Math.PI / 180);
15030
+ const avgLat = (paddedSouth + paddedNorth) / 2;
15031
+ const latRad = avgLat * (Math.PI / 180);
15032
+ const rectWidth = (paddedEast - paddedWest) * (Math.PI / 180) * earthRadius * Math.cos(latRad);
13831
15033
  const rectHeight = (paddedNorth - paddedSouth) * (Math.PI / 180) * earthRadius;
13832
15034
  const frustum = camera.frustum;
13833
15035
  const fovy = frustum.fovy || frustum.fov || C.Math.toRadians(60);
13834
15036
  const canvas = this.viewer.scene.canvas;
13835
15037
  const aspectRatio = canvas.width / canvas.height;
13836
- const fovx = 2 * Math.atan(Math.tan(fovy / 2) * aspectRatio);
13837
15038
  const absPitch = Math.abs(pitch);
13838
- const sinPitch = Math.sin(absPitch) || 0.707;
13839
- const altitudeForHeight = rectHeight / 2 / Math.tan(fovy / 2) / sinPitch;
13840
- const altitudeForWidth = rectWidth / 2 / Math.tan(fovx / 2) / sinPitch;
13841
- const altitude = Math.max(altitudeForHeight, altitudeForWidth, 500) * 1.1;
15039
+ const sinPitch = Math.sin(absPitch);
15040
+ const tanHalfFovy = Math.tan(fovy / 2);
15041
+ let viewHeight;
15042
+ if (absPitch > C.Math.toRadians(80)) {
15043
+ const heightForVertical = rectHeight / (2 * tanHalfFovy);
15044
+ const heightForHorizontal = rectWidth / (2 * tanHalfFovy * aspectRatio);
15045
+ viewHeight = Math.max(heightForVertical, heightForHorizontal);
15046
+ } else {
15047
+ const heightForVertical = sinPitch > 0.1 ? rectHeight / (2 * tanHalfFovy * sinPitch) : rectHeight / (2 * tanHalfFovy);
15048
+ const heightForHorizontal = rectWidth / (2 * tanHalfFovy * aspectRatio);
15049
+ viewHeight = Math.max(heightForVertical, heightForHorizontal);
15050
+ }
15051
+ viewHeight = Math.max(minHeight, Math.min(maxHeight, viewHeight * 1.2));
15052
+ const altitude = validTerrainHeight + viewHeight;
13842
15053
  const destination = C.Cartesian3.fromDegrees(bounds.centerLon, bounds.centerLat, altitude);
13843
15054
  const orientation = { heading, pitch, roll: 0 };
13844
15055
  if (duration > 0) {
@@ -13846,7 +15057,6 @@ var MassPolygonManager = class {
13846
15057
  } else {
13847
15058
  camera.setView({ destination, orientation });
13848
15059
  }
13849
- console.log(`[MassPolygonManager] \u98DE\u884C\u5230\u8FB9\u754C: \u4E2D\u5FC3(${bounds.centerLon.toFixed(6)}, ${bounds.centerLat.toFixed(6)}), \u9AD8\u5EA6: ${altitude.toFixed(0)}m`);
13850
15060
  }
13851
15061
  /**
13852
15062
  * 设置可见性
@@ -13855,7 +15065,184 @@ var MassPolygonManager = class {
13855
15065
  if (this.layerCollection) {
13856
15066
  this.layerCollection.show = visible;
13857
15067
  }
15068
+ if (this.viewer?.scene) {
15069
+ try {
15070
+ this.viewer.scene.requestRender();
15071
+ } catch (e) {
15072
+ }
15073
+ }
15074
+ }
15075
+ /**
15076
+ * 获取图层可见性
15077
+ */
15078
+ getVisibility() {
15079
+ return this.layerCollection?.show ?? false;
15080
+ }
15081
+ /**
15082
+ * 隐藏整个图层
15083
+ */
15084
+ hide() {
15085
+ this.setVisibility(false);
15086
+ }
15087
+ /**
15088
+ * 显示整个图层
15089
+ */
15090
+ show() {
15091
+ this.setVisibility(true);
15092
+ }
15093
+ /**
15094
+ * 切换图层可见性
15095
+ * @returns 切换后的可见性状态
15096
+ */
15097
+ toggleVisibility() {
15098
+ const newVisibility = !this.getVisibility();
15099
+ this.setVisibility(newVisibility);
15100
+ return newVisibility;
15101
+ }
15102
+ // ==================== 静态标签管理 ====================
15103
+ /**
15104
+ * 显示所有多边形的静态标签(异步版本)
15105
+ * 在每个多边形的中心点显示其名称
15106
+ *
15107
+ * 注意:
15108
+ * 1. LabelCollection 不支持 heightReference 属性
15109
+ * 2. 贴地模式下,使用异步方法查询地形高度来正确放置标签
15110
+ * 3. 通过 disableDepthTestDistance 确保标签始终可见
15111
+ * 4. 查询的地形高度会被缓存,供 hover 标签使用
15112
+ */
15113
+ async showStaticLabels() {
15114
+ if (!this.staticLabelCollection) return;
15115
+ const C = this.CesiumNS;
15116
+ this.staticLabelCollection.removeAll();
15117
+ const polygonsWithCenters = [];
15118
+ for (const polygon of this.polygonDataMap.values()) {
15119
+ if (!polygon.name) continue;
15120
+ const center = this.calculatePolygonCentroid(polygon.points);
15121
+ polygonsWithCenters.push({ polygon, center });
15122
+ }
15123
+ if (polygonsWithCenters.length === 0) {
15124
+ this.staticLabelsVisible = true;
15125
+ return;
15126
+ }
15127
+ let labelHeights;
15128
+ if (this.isClampToGround) {
15129
+ const coordinates = polygonsWithCenters.map(({ center }) => ({
15130
+ lon: center.lon,
15131
+ lat: center.lat
15132
+ }));
15133
+ try {
15134
+ const terrainHeights = await queryTerrainHeightsByLonLat(
15135
+ this.CesiumNS,
15136
+ this.viewer,
15137
+ coordinates
15138
+ );
15139
+ labelHeights = terrainHeights.map((h) => h + 2);
15140
+ for (let i = 0; i < polygonsWithCenters.length; i++) {
15141
+ const polygonId = polygonsWithCenters[i].polygon.id;
15142
+ this.terrainHeightCache.set(polygonId, terrainHeights[i]);
15143
+ }
15144
+ console.log(`[MassPolygonManager] \u5F02\u6B65\u67E5\u8BE2\u5730\u5F62\u9AD8\u5EA6\u5B8C\u6210\uFF0C\u8303\u56F4: ${Math.min(...terrainHeights).toFixed(1)}m ~ ${Math.max(...terrainHeights).toFixed(1)}m`);
15145
+ } catch (error) {
15146
+ console.warn("[MassPolygonManager] \u5F02\u6B65\u67E5\u8BE2\u5730\u5F62\u9AD8\u5EA6\u5931\u8D25\uFF0C\u4F7F\u7528\u9ED8\u8BA4\u9AD8\u5EA6:", error);
15147
+ labelHeights = polygonsWithCenters.map(() => 2);
15148
+ }
15149
+ } else {
15150
+ labelHeights = polygonsWithCenters.map(() => this.polygonHeight);
15151
+ }
15152
+ if (this.isDestroyed || !this.staticLabelCollection) {
15153
+ console.warn("[MassPolygonManager] \u5B9E\u4F8B\u5DF2\u9500\u6BC1\uFF0C\u8DF3\u8FC7\u521B\u5EFA\u6807\u7B7E");
15154
+ return;
15155
+ }
15156
+ for (let i = 0; i < polygonsWithCenters.length; i++) {
15157
+ const { polygon, center } = polygonsWithCenters[i];
15158
+ const labelHeight = labelHeights[i];
15159
+ const position = C.Cartesian3.fromDegrees(center.lon, center.lat, labelHeight);
15160
+ this.staticLabelCollection.add({
15161
+ position,
15162
+ text: polygon.name,
15163
+ font: this.staticLabelStyle.font,
15164
+ fillColor: C.Color.fromCssColorString(this.staticLabelStyle.fillColor),
15165
+ outlineColor: C.Color.fromCssColorString(this.staticLabelStyle.outlineColor),
15166
+ outlineWidth: this.staticLabelStyle.outlineWidth,
15167
+ style: C.LabelStyle.FILL_AND_OUTLINE,
15168
+ showBackground: this.staticLabelStyle.showBackground,
15169
+ backgroundColor: C.Color.fromCssColorString(this.staticLabelStyle.backgroundColor),
15170
+ backgroundPadding: new C.Cartesian2(this.staticLabelStyle.backgroundPadding[0], this.staticLabelStyle.backgroundPadding[1]),
15171
+ pixelOffset: new C.Cartesian2(this.staticLabelStyle.pixelOffset[0], this.staticLabelStyle.pixelOffset[1]),
15172
+ scale: this.staticLabelStyle.scale,
15173
+ verticalOrigin: C.VerticalOrigin.CENTER,
15174
+ horizontalOrigin: C.HorizontalOrigin.CENTER,
15175
+ // 禁用深度测试,确保标签始终可见(不会被地形遮挡)
15176
+ disableDepthTestDistance: Number.POSITIVE_INFINITY
15177
+ });
15178
+ }
15179
+ this.staticLabelsVisible = true;
15180
+ this.viewer.scene.requestRender();
15181
+ console.log(`[MassPolygonManager] \u5DF2\u663E\u793A ${this.staticLabelCollection.length} \u4E2A\u9759\u6001\u6807\u7B7E`);
15182
+ }
15183
+ /**
15184
+ * 预加载所有多边形质心的地形高度(用于 hover 标签)
15185
+ * 在贴地模式下,建议在 create() 后调用此方法
15186
+ */
15187
+ async preloadTerrainHeights() {
15188
+ if (!this.isClampToGround) return;
15189
+ const polygons = Array.from(this.polygonDataMap.values());
15190
+ if (polygons.length === 0) return;
15191
+ const coordinates = polygons.map((polygon) => {
15192
+ const center = this.calculatePolygonCentroid(polygon.points);
15193
+ return { lon: center.lon, lat: center.lat };
15194
+ });
15195
+ try {
15196
+ const terrainHeights = await queryTerrainHeightsByLonLat(
15197
+ this.CesiumNS,
15198
+ this.viewer,
15199
+ coordinates
15200
+ );
15201
+ for (let i = 0; i < polygons.length; i++) {
15202
+ this.terrainHeightCache.set(polygons[i].id, terrainHeights[i]);
15203
+ }
15204
+ console.log(`[MassPolygonManager] \u9884\u52A0\u8F7D\u5730\u5F62\u9AD8\u5EA6\u5B8C\u6210: ${polygons.length} \u4E2A\u591A\u8FB9\u5F62`);
15205
+ } catch (error) {
15206
+ console.warn("[MassPolygonManager] \u9884\u52A0\u8F7D\u5730\u5F62\u9AD8\u5EA6\u5931\u8D25:", error);
15207
+ }
15208
+ }
15209
+ /**
15210
+ * 隐藏所有多边形的静态标签
15211
+ */
15212
+ hideStaticLabels() {
15213
+ if (!this.staticLabelCollection) return;
15214
+ this.staticLabelCollection.removeAll();
15215
+ this.staticLabelsVisible = false;
13858
15216
  this.viewer.scene.requestRender();
15217
+ console.log("[MassPolygonManager] \u5DF2\u9690\u85CF\u9759\u6001\u6807\u7B7E");
15218
+ }
15219
+ /**
15220
+ * 切换静态标签显示状态
15221
+ * @returns 切换后的显示状态
15222
+ */
15223
+ async toggleStaticLabels() {
15224
+ if (this.staticLabelsVisible) {
15225
+ this.hideStaticLabels();
15226
+ } else {
15227
+ await this.showStaticLabels();
15228
+ }
15229
+ return this.staticLabelsVisible;
15230
+ }
15231
+ /**
15232
+ * 获取静态标签显示状态
15233
+ */
15234
+ getStaticLabelsVisible() {
15235
+ return this.staticLabelsVisible;
15236
+ }
15237
+ /**
15238
+ * 设置静态标签样式
15239
+ * @param style 样式配置(部分)
15240
+ */
15241
+ async setStaticLabelStyle(style) {
15242
+ this.staticLabelStyle = { ...this.staticLabelStyle, ...style };
15243
+ if (this.staticLabelsVisible) {
15244
+ await this.showStaticLabels();
15245
+ }
13859
15246
  }
13860
15247
  /**
13861
15248
  * 更新样式(需要重新创建所有多边形)
@@ -13879,6 +15266,10 @@ var MassPolygonManager = class {
13879
15266
  this.clickHandler.destroy();
13880
15267
  this.clickHandler = void 0;
13881
15268
  }
15269
+ if (this.rightClickHandler) {
15270
+ this.rightClickHandler.destroy();
15271
+ this.rightClickHandler = void 0;
15272
+ }
13882
15273
  this.highlightedId = void 0;
13883
15274
  this.selectedId = void 0;
13884
15275
  this.originalHighlightFillColor = void 0;
@@ -13886,20 +15277,35 @@ var MassPolygonManager = class {
13886
15277
  this.originalSelectFillColor = void 0;
13887
15278
  this.originalSelectOutlineColor = void 0;
13888
15279
  this.hoverLabel = void 0;
13889
- if (this.layerCollection) {
13890
- this.viewer.scene.primitives.remove(this.layerCollection);
15280
+ this.highlightPrimitive = void 0;
15281
+ this.selectPrimitive = void 0;
15282
+ if (this.layerCollection && this.viewer?.scene?.primitives) {
15283
+ try {
15284
+ this.viewer.scene.primitives.remove(this.layerCollection);
15285
+ } catch (e) {
15286
+ }
13891
15287
  this.layerCollection = void 0;
13892
15288
  }
13893
15289
  this.primitive = void 0;
13894
15290
  this.outlinePrimitive = void 0;
15291
+ this.groundOutlinePrimitive = void 0;
13895
15292
  this.labelCollection = void 0;
15293
+ this.staticLabelCollection = void 0;
15294
+ this.staticLabelsVisible = false;
13896
15295
  this.polygonDataMap.clear();
13897
- this.viewer.scene.requestRender();
15296
+ this.terrainHeightCache.clear();
15297
+ if (this.viewer?.scene) {
15298
+ try {
15299
+ this.viewer.scene.requestRender();
15300
+ } catch (e) {
15301
+ }
15302
+ }
13898
15303
  }
13899
15304
  /**
13900
15305
  * 销毁并释放资源
13901
15306
  */
13902
15307
  destroy() {
15308
+ this.isDestroyed = true;
13903
15309
  this.clear();
13904
15310
  }
13905
15311
  /**
@@ -14285,6 +15691,6 @@ var LODManager = class {
14285
15691
  var placeholder = { ready: true };
14286
15692
  var droneModelUrl = wurenji_default;
14287
15693
 
14288
- export { AirplaneCursor, CZMLManager, CameraEventBus, CameraFOVController, CameraManager, Emitter, FlightSimulator, FrustumPyramid, LODManager, LayerManager, MassPolygonManager, PathSafetyChecker, PointCloudPicker, PolygonEditor, RealtimeFlightTracker, SceneManager, Selector, StateManager, assertCesiumAssetsConfigured, bringDataSourceToTop, bringPrimitiveCollectionToTop, calculateAbsoluteHeight, calculateBoundsDiagonal, calculateDistance, calculateDistanceAndMidpoint, calculateGeoBounds, calculateHeadingBetweenPoints, calculateMidpoint, calculateRelativeHeight, configureCesiumAssets, configureCesiumIonToken, convertPathToSinofly, convertSinoflyWayline, convertSinoflyWaylines, droneModelUrl, ensureCesiumIonToken, ensureLayerAbove, expandBounds, getCesiumBaseUrl, getCesiumIonToken, getDataSourceIndex, getDataSourceOrder, globalCameraEventBus, globalState, isPointInBounds, mergeBounds, placeholder, queryTerrainHeightAsync, queryTerrainHeightByLonLat, queryTerrainHeightByLonLatSync, queryTerrainHeightSync, queryTerrainHeights, queryTerrainHeightsByLonLat, renderFlightPath, renderFlightPathPreview, toggle2D3D, version, versionInfo };
15694
+ export { AirplaneCursor, CZMLManager, CameraEventBus, CameraFOVController, CameraManager, Emitter, FlightSimulator, FrustumPyramid, ImageryManager, LODManager, LayerManager, MassPolygonManager, PathSafetyChecker, PointCloudPicker, PolygonEditor, RealtimeFlightTracker, SceneManager, Selector, StateManager, TerrainManager, TilesetManager, assertCesiumAssetsConfigured, bringDataSourceToTop, bringPrimitiveCollectionToTop, calculateAbsoluteHeight, calculateBoundsDiagonal, calculateDistance, calculateDistanceAndMidpoint, calculateGeoBounds, calculateHeadingBetweenPoints, calculateMidpoint, calculateRelativeHeight, configureCesiumAssets, configureCesiumIonToken, convertPathToSinofly, convertSinoflyWayline, convertSinoflyWaylines, droneModelUrl, ensureCesiumIonToken, ensureLayerAbove, expandBounds, getCesiumBaseUrl, getCesiumIonToken, getDataSourceIndex, getDataSourceOrder, globalCameraEventBus, globalState, isPointInBounds, mergeBounds, placeholder, queryTerrainHeightAsync, queryTerrainHeightByLonLat, queryTerrainHeightByLonLatSync, queryTerrainHeightSync, queryTerrainHeights, queryTerrainHeightsByLonLat, renderFlightPath, renderFlightPathPreview, toggle2D3D, version, versionInfo };
14289
15695
  //# sourceMappingURL=index.js.map
14290
15696
  //# sourceMappingURL=index.js.map