raintee-maputils 1.0.16 → 1.0.18

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
@@ -1,308 +1,3 @@
1
- class OptionsControl {
2
- constructor(args) {
3
- const { options, onConfirm } = args;
4
-
5
- this._container = null;
6
- this._map = null;
7
-
8
- this._options = options || [];
9
- this._onConfirm = onConfirm || (() => { });
10
-
11
- if (!this._options || !Array.isArray(this._options)) {
12
- console.error('请传入有效的 options 参数(数组)');
13
- }
14
- }
15
-
16
- onAdd(map) {
17
- this._map = map;
18
-
19
- // 创建控件外层容器
20
- this._container = document.createElement('div');
21
- this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group custom-options-control';
22
-
23
- // 创建主按钮(初始只显示“三维模型”文字,点击展开)
24
- const mainButton = document.createElement('button');
25
- mainButton.style.cssText = `
26
- width: 100%;
27
- height: 100%;
28
- padding: 0.25rem 0.5rem;
29
- margin: 0;
30
- border: 1px solid #ccc;
31
- background: white;
32
- border-radius: 4px;
33
- cursor: pointer;
34
- font-size: 14px;
35
- text-align: left;
36
- `;
37
- mainButton.textContent = '三维模型';
38
- mainButton.addEventListener('click', () => this._toggleExpanded());
39
-
40
- this._container.appendChild(mainButton);
41
-
42
- // 创建隐藏的选项面板(默认不显示)
43
- this._panel = document.createElement('div');
44
- this._panel.style.display = 'none';
45
- this._panel.style.padding = '10px';
46
- this._container.appendChild(this._panel);
47
-
48
- // 创建选项列表和按钮组
49
- this._renderOptionsPanel();
50
-
51
- return this._container;
52
- }
53
-
54
- _toggleExpanded() {
55
- const isExpanded = this._panel.style.display !== 'none';
56
- this._setExpanded(!isExpanded);
57
- }
58
-
59
- _setExpanded(isExpanded) {
60
- this._panel.style.display = isExpanded ? 'block' : 'none';
61
- }
62
-
63
- _renderOptionsPanel() {
64
- // 清空面板内容
65
- this._panel.innerHTML = '';
66
-
67
- // 创建复选框选项列表
68
- this._options.forEach((opt) => {
69
- const optionDiv = document.createElement('div');
70
- optionDiv.style.marginBottom = '6px';
71
-
72
- const label = document.createElement('label');
73
- label.style.display = 'flex';
74
- label.style.alignItems = 'center';
75
- label.style.cursor = 'pointer';
76
-
77
- const checkbox = document.createElement('input');
78
- checkbox.type = 'checkbox';
79
- checkbox.value = opt.value;
80
- checkbox.style.marginRight = '8px';
81
-
82
- // 绑定选中状态
83
- checkbox.checked = this._getSelectedOption(opt.value);
84
- checkbox.addEventListener('change', () => this._toggleOption(opt.value));
85
-
86
- const span = document.createElement('span');
87
- span.textContent = opt.label;
88
-
89
- label.appendChild(checkbox);
90
- label.appendChild(span);
91
- optionDiv.appendChild(label);
92
- this._panel.appendChild(optionDiv);
93
- });
94
-
95
- // 创建按钮组:取消 / 确定
96
- const buttonGroup = document.createElement('div');
97
- buttonGroup.style.marginTop = '12px';
98
- buttonGroup.style.display = 'flex';
99
- buttonGroup.style.gap = '8px';
100
- buttonGroup.style.justifyContent = 'flex-end';
101
-
102
- const cancelButton = document.createElement('button');
103
- cancelButton.textContent = '取消';
104
- cancelButton.style.cssText = `
105
- padding: 6px 12px;
106
- background: #ccc;
107
- border: none;
108
- border-radius: 4px;
109
- cursor: pointer;
110
- width: auto;
111
- `;
112
- cancelButton.addEventListener('click', () => this._handleCancel());
113
-
114
- const confirmButton = document.createElement('button');
115
- confirmButton.textContent = '确定';
116
- confirmButton.style.cssText = `
117
- padding: 6px 12px;
118
- background: #007cba;
119
- color: white;
120
- border: none;
121
- border-radius: 4px;
122
- cursor: pointer;
123
- width: auto;
124
- `;
125
- confirmButton.addEventListener('click', () => this._handleConfirm());
126
-
127
- buttonGroup.appendChild(cancelButton);
128
- buttonGroup.appendChild(confirmButton);
129
- this._panel.appendChild(buttonGroup);
130
- }
131
-
132
- _getSelectedOption(value) {
133
- return this._selectedOptions?.includes(value) || false;
134
- }
135
-
136
- _toggleOption(value) {
137
- if (!this._selectedOptions) this._selectedOptions = [];
138
-
139
- const idx = this._selectedOptions.indexOf(value);
140
- if (idx > -1) {
141
- this._selectedOptions.splice(idx, 1);
142
- } else {
143
- this._selectedOptions.push(value);
144
- }
145
-
146
- // 可选:重新渲染复选框状态(如果需要动态更新 UI,但目前 onchange 已处理)
147
- }
148
-
149
- _handleConfirm() {
150
- this._setExpanded(false);
151
-
152
- this._onConfirm({
153
- selectedOptions: this._selectedOptions || [],
154
- unselectedOptions: this._options.filter((item) => !this._selectedOptions?.includes(item.value)),
155
- allOptions: this._options,
156
- });
157
- }
158
-
159
- _handleCancel() {
160
- this._setExpanded(false);
161
- }
162
-
163
- onRemove() {
164
- if (this._container && this._container.parentNode) {
165
- this._container.parentNode.removeChild(this._container);
166
- }
167
- }
168
- }
169
- const CustomOptionsControlBuilder = {
170
- build(args) {
171
- return new OptionsControl(args);
172
- }
173
- };
174
-
175
- // 自定义控件类:ToggleControl
176
- class ToggleControl {
177
- /**
178
- * 构造函数
179
- * @param {Object} options
180
- * @param {string} options.name - 控件名称(用于识别,可选展示)
181
- * @param {string} options.field - 控制的字段名,对应 map.SourceMap[field]
182
- * @param {boolean} options.defaultValue - 默认值(当 field 未定义时使用)
183
- * @param {string} options.svgIcon - SVG 图标字符串,例如 '<svg>...</svg>'
184
- * @param {Function} [options.onToggle] - 可选的回调函数,状态切换后调用,参数为最新的布尔值
185
- */
186
- constructor({ name, field, defaultValue = false, svgIcon, onToggle }) {
187
- this.name = name;
188
- this.field = field;
189
- this.defaultValue = defaultValue;
190
- this.svgIcon = svgIcon;
191
- this.onToggle = onToggle; // ✅ 新增:回调函数
192
-
193
- // 控件容器
194
- this._container = null;
195
- this._button = null;
196
- this.isActive = false; // 当前是否为激活状态(高亮/true)
197
- }
198
-
199
- // Mapbox 要求的 onAdd 方法
200
- onAdd(map) {
201
- this.map = map;
202
-
203
- // 创建外层容器 div
204
- this._container = document.createElement('div');
205
- this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
206
-
207
- // 创建 button 元素
208
- this._button = document.createElement('button');
209
- this._button.type = 'button';
210
- this._button.innerHTML = this.svgIcon;
211
-
212
- // 设置按钮样式
213
- this._button.style.cursor = 'pointer';
214
- this._button.style.border = 'none';
215
- this._button.style.background = 'none';
216
- this._button.style.padding = '0';
217
- this._button.style.display = 'flex';
218
- this._button.style.alignItems = 'center';
219
- this._button.style.justifyContent = 'center';
220
-
221
- // 初始化状态
222
- this.updateStatus();
223
-
224
- // 绑定点击事件
225
- this._button.addEventListener('click', () => {
226
- this.toggle();
227
- });
228
-
229
- this._container.appendChild(this._button);
230
-
231
- return this._container;
232
- }
233
-
234
- // Mapbox 要求的 onRemove 方法
235
- onRemove() {
236
- if (this._container && this._container.parentNode) {
237
- this._container.parentNode.removeChild(this._container);
238
- }
239
- this.map = null;
240
- this._container = null;
241
- this._button = null;
242
- }
243
-
244
- // 更新控件状态(根据当前 field 值)
245
- updateStatus() {
246
- // 从 map.SourceMap 中获取当前 field 的值,如果不存在则使用 defaultValue
247
- let currentValue = this.defaultValue;
248
-
249
- if (
250
- this.map &&
251
- this.map.SourceMap &&
252
- typeof this.map.SourceMap[this.field] === 'boolean'
253
- ) {
254
- currentValue = this.map.SourceMap[this.field];
255
- }
256
-
257
- this.isActive = currentValue; // true 表示激活,false 表示未激活
258
-
259
- // 更新按钮样式
260
- if (this.isActive) {
261
- this._button.style.opacity = '1';
262
- this._button.style.filter = 'none';
263
- } else {
264
- this._button.style.opacity = '0.4';
265
- this._button.style.filter = 'grayscale(100%)';
266
- }
267
- }
268
-
269
- // 切换状态
270
- toggle() {
271
- // 获取当前值
272
- let currentValue = this.defaultValue;
273
-
274
- if (
275
- this.map &&
276
- this.map.SourceMap &&
277
- typeof this.map.SourceMap[this.field] === 'boolean'
278
- ) {
279
- currentValue = this.map.SourceMap[this.field];
280
- }
281
-
282
- // 切换值
283
- const newValue = !currentValue;
284
-
285
- // 更新到 map.SourceMap 中
286
- if (this.map && this.map.SourceMap) {
287
- this.map.SourceMap[this.field] = newValue;
288
- }
289
-
290
- // 更新内部状态
291
- this.isActive = newValue;
292
-
293
- // 更新 UI 样式
294
- this.updateStatus();
295
-
296
- // ✅ 调用回调函数(如果传入了 onToggle),并传入最新的值
297
- this.onToggle?.(newValue); // 安全调用,仅当函数存在时才执行
298
- }
299
- }
300
- const CustomToggleControlBuilder = {
301
- build(args) {
302
- return new ToggleControl(args);
303
- }
304
- };
305
-
306
1
  const predefineColorSet = {
307
2
  '默认单色预设': [{
308
3
  Property: '默认',
@@ -5856,272 +5551,567 @@ function difference2(features) {
5856
5551
  return multiPolygon(differenced, properties);
5857
5552
  }
5858
5553
 
5859
- /**
5860
- * 传入一个 features 数组(至少包含两个要素),
5861
- * 计算第二个要素(features[1])中,不在第一个要素(features[0])内部的部分,
5862
- * 即:features[1] 减去 features[0],返回这一部分(GeoJSON Feature)。
5863
- *
5864
- * @param {Array<turf.Feature>} features - GeoJSON Feature 数组,至少包含两个要素
5865
- * @returns {turf.Feature | null} - 返回 features[1] 减去 features[0] 的部分,如果出错或无效返回 null
5866
- */
5867
- function getFeatureOutsidePart(features) {
5868
- if (!Array.isArray(features) || features.length < 2) {
5869
- console.error('Input must be an array of at least two GeoJSON Features.');
5870
- return null;
5554
+ /**
5555
+ * 传入一个 features 数组(至少包含两个要素),
5556
+ * 计算第二个要素(features[1])中,不在第一个要素(features[0])内部的部分,
5557
+ * 即:features[1] 减去 features[0],返回这一部分(GeoJSON Feature)。
5558
+ *
5559
+ * @param {Array<turf.Feature>} features - GeoJSON Feature 数组,至少包含两个要素
5560
+ * @returns {turf.Feature | null} - 返回 features[1] 减去 features[0] 的部分,如果出错或无效返回 null
5561
+ */
5562
+ function getFeatureOutsidePart(features) {
5563
+ if (!Array.isArray(features) || features.length < 2) {
5564
+ console.error('Input must be an array of at least two GeoJSON Features.');
5565
+ return null;
5566
+ }
5567
+
5568
+ // 我们要计算的是:featureB - featureA,即 featureB 的外部部分(不在 featureA 中的部分)
5569
+ const differenceResult = difference2(featureCollection(features));
5570
+
5571
+ if (!differenceResult) {
5572
+ console.warn('Difference result is empty or invalid. The second feature may be fully inside the first one.');
5573
+ return null; // 两者无差异,或者输入几何有问题
5574
+ }
5575
+
5576
+ return differenceResult; // 返回的就是 features[1] 在 features[0] 外部的部分
5577
+ }
5578
+ const RT_FitBoundsMapbox = (map, featureCollection) => {
5579
+ try {
5580
+ if (!map || !featureCollection) {
5581
+ console.error('RT_FitBoundsMapbox: map 或 featureCollection 参数不能为空');
5582
+ return;
5583
+ }
5584
+
5585
+
5586
+ const boundsArray = bbox(featureCollection); // [minLng, minLat, maxLng, maxLat]
5587
+
5588
+ if (!boundsArray || boundsArray.length !== 4) {
5589
+ console.error('RT_FitBoundsMapbox: 无法计算有效的包围盒');
5590
+ return;
5591
+ }
5592
+
5593
+ const [minLng, minLat, maxLng, maxLat] = boundsArray;
5594
+
5595
+ const bounds = [
5596
+ [minLng, minLat], // 左下角
5597
+ [maxLng, maxLat] // 右上角
5598
+ ];
5599
+
5600
+ map.fitBounds(bounds, {
5601
+ padding: 20, // 可选:边距(单位像素)
5602
+ maxZoom: 15, // 可选:最大缩放级别,避免太近
5603
+ duration: 1000, // 可选:动画时长(毫秒)
5604
+ easing: (t) => {
5605
+ return t;
5606
+ }
5607
+ });
5608
+ } catch (error) {
5609
+ console.error('RT_FitBoundsMapbox: 拟合地图边界时出错', error);
5610
+ }
5611
+
5612
+ };
5613
+ const RT_FitBoundsMapboxNative = (map, featureCollection) => {
5614
+ try {
5615
+ if (!map || !featureCollection) {
5616
+ console.error('RT_FitBoundsMapbox: map 或 featureCollection 参数不能为空');
5617
+ return;
5618
+ }
5619
+
5620
+ // ✅ 手写函数:计算 FeatureCollection 的 Bounding Box [minLng, minLat, maxLng, maxLat]
5621
+ const calculateBoundingBox = (featureCollection) => {
5622
+ let minLng = Infinity;
5623
+ let minLat = Infinity;
5624
+ let maxLng = -Infinity;
5625
+ let maxLat = -Infinity;
5626
+
5627
+ // 遍历每个 feature
5628
+ for (const feature of featureCollection.features) {
5629
+ const coords = feature.geometry.coordinates;
5630
+
5631
+ // 递归遍历坐标(支持 Point、LineString、Polygon、Multi 等)
5632
+ const traverseCoordinates = (coords) => {
5633
+ if (!Array.isArray(coords)) return;
5634
+
5635
+ // 判断坐标类型:
5636
+ if (typeof coords[0] === 'number') {
5637
+ // 🟢 这是一个点 [lng, lat]
5638
+ const [lng, lat] = coords;
5639
+ if (lng < minLng) minLng = lng;
5640
+ if (lng > maxLng) maxLng = lng;
5641
+ if (lat < minLat) minLat = lat;
5642
+ if (lat > maxLat) maxLat = lat;
5643
+ } else if (Array.isArray(coords[0])) {
5644
+ // 🔵 可能是 LineString、Polygon、MultiPoint 等(嵌套数组)
5645
+ for (const subCoords of coords) {
5646
+ traverseCoordinates(subCoords);
5647
+ }
5648
+ }
5649
+ // 注意:更复杂的 GeometryCollection 或 MultiPolygon 也按类似方式递归,但此处已覆盖大部分情况
5650
+ };
5651
+
5652
+ traverseCoordinates(coords);
5653
+ }
5654
+
5655
+ // 如果没有有效的点(比如 featureCollection 为空或没有坐标)
5656
+ if (
5657
+ minLng === Infinity ||
5658
+ minLat === Infinity ||
5659
+ maxLng === -Infinity ||
5660
+ maxLat === -Infinity
5661
+ ) {
5662
+ console.error('RT_FitBoundsMapbox: 未找到有效的坐标点');
5663
+ return null;
5664
+ }
5665
+
5666
+ return [minLng, minLat, maxLng, maxLat];
5667
+ };
5668
+
5669
+ // ✅ 调用手写函数,计算 bounding box
5670
+ const boundsArray = calculateBoundingBox(featureCollection);
5671
+
5672
+ if (!boundsArray) {
5673
+ console.error('RT_FitBoundsMapbox: 无法计算有效的包围盒');
5674
+ return;
5675
+ }
5676
+
5677
+ const [minLng, minLat, maxLng, maxLat] = boundsArray;
5678
+
5679
+ const bounds = [
5680
+ [minLng, minLat], // 左下角
5681
+ [maxLng, maxLat] // 右上角
5682
+ ];
5683
+ console.log(`output->bounds`, bounds);
5684
+ bounds.forEach(item => {
5685
+ item.forEach((i, index) => {
5686
+ item[index] = Number((i).toFixed(6));
5687
+ });
5688
+ });
5689
+ // ✅ 调用 Mapbox 的 fitBounds
5690
+ map.fitBounds(bounds, {
5691
+ padding: 50, // 可选:边距(单位像素)
5692
+ maxZoom: 15, // 可选:最大缩放级别,避免太近
5693
+ duration: 1000, // 可选:动画时长(毫秒)
5694
+ });
5695
+
5696
+ } catch (error) {
5697
+ console.error('RT_FitBoundsMapbox: 拟合地图边界时出错', error);
5698
+ }
5699
+ };
5700
+
5701
+ var RainteeGISUtil = /*#__PURE__*/Object.freeze({
5702
+ __proto__: null,
5703
+ RT_FitBoundsMapbox: RT_FitBoundsMapbox,
5704
+ RT_FitBoundsMapboxNative: RT_FitBoundsMapboxNative,
5705
+ getFeatureOutsidePart: getFeatureOutsidePart
5706
+ });
5707
+
5708
+ const treeDataAdapter = (data) => {
5709
+ let i = 0;
5710
+ let treeData = [];
5711
+ let checkedKeys = [];
5712
+ data.forEach((item) => {
5713
+ let kvs = item.id.split(';').map(kvstr => kvstr.split('='));
5714
+ let groups = kvs.find(item => item[0] === 'Main')[1].split('/');
5715
+ let layerName = kvs.find(item => item[0] === 'CN')[1];
5716
+ let currentNode = treeData;
5717
+ groups.forEach((group, index) => {
5718
+ currentNode.find(item => item.label === group) || i++;
5719
+ currentNode.find(item => item.label === group) || currentNode.push({
5720
+ id: i,
5721
+ label: group,
5722
+ children: []
5723
+ });
5724
+ currentNode = currentNode.find(item => item.label === group).children;
5725
+ });
5726
+ i++;
5727
+ currentNode.push({
5728
+ id: i,
5729
+ label: layerName,
5730
+ fullLayerName: groups.length === 0 ? layerName : groups.join('-') + '-' + layerName,
5731
+ layerId: item.id,
5732
+ opacity: '1.0',
5733
+ });
5734
+ if (item.layout && item.layout.visibility == 'visible') {
5735
+ checkedKeys.push(i);
5736
+ }
5737
+ });
5738
+ return { treeData, checkedKeys }
5739
+ };
5740
+ const treeDataAdapterNext = (data) => {
5741
+ let i = 0;
5742
+ let treeData = [];
5743
+ let checkedKeys = [];
5744
+ data.forEach((item) => {
5745
+ let kvs = item.id.split(';').map(kvstr => kvstr.split('='));
5746
+ let groups = kvs.find(item => item[0] === 'Main')[1].split('/');
5747
+ let layerName = kvs.find(item => item[0] === 'CN')[1];
5748
+ let currentNode = treeData;
5749
+ groups.length === 0 ? layerName : groups.join('-') + '-' + layerName;
5750
+ groups.forEach((group, index) => {
5751
+ currentNode.find(item => item.label === group) || i++;
5752
+ currentNode.find(item => item.label === group) || currentNode.push({
5753
+ id: `TreeId=${i};`,
5754
+ label: group,
5755
+ children: []
5756
+ });
5757
+ currentNode = currentNode.find(item => item.label === group).children;
5758
+ });
5759
+ i++;
5760
+ currentNode.push({
5761
+ id: item.id,
5762
+ label: layerName,
5763
+ fullLayerName: getLayerIdFidld(item.id, 'Main') + '-' + getLayerIdFidld(item.id, 'CN'),
5764
+ layerId: item.id,
5765
+ opacity: '1.0',
5766
+ });
5767
+ if (item.layout && item.layout.visibility == 'visible') {
5768
+ checkedKeys.push(i);
5769
+ }
5770
+ });
5771
+ return { treeData, checkedKeys }
5772
+ };
5773
+ const getLayerIdFidld = (str, field) => {
5774
+ let kvs = str.split(';').map(kvstr => kvstr.split('='));
5775
+ let layerId = kvs.find(item => item[0] === field)[1];
5776
+ return layerId
5777
+ };
5778
+ /**
5779
+ * 生成唯一ID字符串
5780
+ * @param {string} projectId 项目ID
5781
+ * @param {string} objectTypeId 对象种类ID
5782
+ * @param {string} groupId 对象分组ID
5783
+ * @param {string} objectId 对象ID
5784
+ * @returns {string} 唯一ID字符串
5785
+ */
5786
+ const GenerateUniqueId = (
5787
+ projectId,
5788
+ objectTypeId,
5789
+ groupId,
5790
+ objectId
5791
+ ) => {
5792
+ // 将参数组合成一个字符串
5793
+ const combinedString = `${projectId}|${objectTypeId}|${groupId}|${objectId}`;
5794
+
5795
+ // 使用简单的哈希函数生成固定长度的字符串
5796
+ let hash = 0;
5797
+ for (let i = 0; i < combinedString.length; i++) {
5798
+ const char = combinedString.charCodeAt(i);
5799
+ hash = (hash << 5) - hash + char;
5800
+ hash = hash & hash; // Convert to 32bit integer
5801
+ }
5802
+
5803
+ // 转换为16进制字符串并补零到8位
5804
+ const hexHash = (hash >>> 0).toString(16).padStart(8, "0");
5805
+
5806
+ // 添加前缀和分隔符增强可读性
5807
+ return `UID-${projectId.slice(0, 2)}-${objectTypeId.slice(
5808
+ 0,
5809
+ 2
5810
+ )}-${groupId.slice(0, 2)}-${hexHash}`;
5811
+ };
5812
+
5813
+ var RainteeSourceMapTool = /*#__PURE__*/Object.freeze({
5814
+ __proto__: null,
5815
+ GenerateUniqueId: GenerateUniqueId,
5816
+ getLayerIdFidld: getLayerIdFidld,
5817
+ treeDataAdapter: treeDataAdapter,
5818
+ treeDataAdapterNext: treeDataAdapterNext
5819
+ });
5820
+
5821
+ class CustomOptionsControl {
5822
+ constructor(args) {
5823
+ const { options, onConfirm } = args;
5824
+
5825
+ this._container = null;
5826
+ this._map = null;
5827
+
5828
+ this._options = options || [];
5829
+ this._onConfirm = onConfirm || (() => { });
5830
+
5831
+ if (!this._options || !Array.isArray(this._options)) {
5832
+ console.error('请传入有效的 options 参数(数组)');
5833
+ }
5871
5834
  }
5872
5835
 
5873
- // 我们要计算的是:featureB - featureA,即 featureB 的外部部分(不在 featureA 中的部分)
5874
- const differenceResult = difference2(featureCollection(features));
5836
+ onAdd(map) {
5837
+ this._map = map;
5875
5838
 
5876
- if (!differenceResult) {
5877
- console.warn('Difference result is empty or invalid. The second feature may be fully inside the first one.');
5878
- return null; // 两者无差异,或者输入几何有问题
5839
+ // 创建控件外层容器
5840
+ this._container = document.createElement('div');
5841
+ this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group custom-options-control';
5842
+
5843
+ // 创建主按钮(初始只显示“三维模型”文字,点击展开)
5844
+ const mainButton = document.createElement('button');
5845
+ mainButton.style.cssText = `
5846
+ width: 100%;
5847
+ height: 100%;
5848
+ padding: 0.25rem 0.5rem;
5849
+ margin: 0;
5850
+ border: 1px solid #ccc;
5851
+ background: white;
5852
+ border-radius: 4px;
5853
+ cursor: pointer;
5854
+ font-size: 14px;
5855
+ text-align: left;
5856
+ `;
5857
+ mainButton.textContent = '三维模型';
5858
+ mainButton.addEventListener('click', () => this._toggleExpanded());
5859
+
5860
+ this._container.appendChild(mainButton);
5861
+
5862
+ // 创建隐藏的选项面板(默认不显示)
5863
+ this._panel = document.createElement('div');
5864
+ this._panel.style.display = 'none';
5865
+ this._panel.style.padding = '10px';
5866
+ this._container.appendChild(this._panel);
5867
+
5868
+ // 创建选项列表和按钮组
5869
+ this._renderOptionsPanel();
5870
+
5871
+ return this._container;
5879
5872
  }
5880
5873
 
5881
- return differenceResult; // 返回的就是 features[1] 在 features[0] 外部的部分
5882
- }
5883
- const RT_FitBoundsMapbox = (map, featureCollection) => {
5884
- try {
5885
- if (!map || !featureCollection) {
5886
- console.error('RT_FitBoundsMapbox: map 或 featureCollection 参数不能为空');
5887
- return;
5888
- }
5874
+ _toggleExpanded() {
5875
+ const isExpanded = this._panel.style.display !== 'none';
5876
+ this._setExpanded(!isExpanded);
5877
+ }
5889
5878
 
5879
+ _setExpanded(isExpanded) {
5880
+ this._panel.style.display = isExpanded ? 'block' : 'none';
5881
+ }
5890
5882
 
5891
- const boundsArray = bbox(featureCollection); // [minLng, minLat, maxLng, maxLat]
5883
+ _renderOptionsPanel() {
5884
+ // 清空面板内容
5885
+ this._panel.innerHTML = '';
5892
5886
 
5893
- if (!boundsArray || boundsArray.length !== 4) {
5894
- console.error('RT_FitBoundsMapbox: 无法计算有效的包围盒');
5895
- return;
5896
- }
5887
+ // 创建复选框选项列表
5888
+ this._options.forEach((opt) => {
5889
+ const optionDiv = document.createElement('div');
5890
+ optionDiv.style.marginBottom = '6px';
5897
5891
 
5898
- const [minLng, minLat, maxLng, maxLat] = boundsArray;
5892
+ const label = document.createElement('label');
5893
+ label.style.display = 'flex';
5894
+ label.style.alignItems = 'center';
5895
+ label.style.cursor = 'pointer';
5899
5896
 
5900
- const bounds = [
5901
- [minLng, minLat], // 左下角
5902
- [maxLng, maxLat] // 右上角
5903
- ];
5897
+ const checkbox = document.createElement('input');
5898
+ checkbox.type = 'checkbox';
5899
+ checkbox.value = opt.value;
5900
+ checkbox.style.marginRight = '8px';
5904
5901
 
5905
- map.fitBounds(bounds, {
5906
- padding: 20, // 可选:边距(单位像素)
5907
- maxZoom: 15, // 可选:最大缩放级别,避免太近
5908
- duration: 1000, // 可选:动画时长(毫秒)
5909
- easing: (t) => {
5910
- return t;
5911
- }
5902
+ // 绑定选中状态
5903
+ checkbox.checked = this._getSelectedOption(opt.value);
5904
+ checkbox.addEventListener('change', () => this._toggleOption(opt.value));
5905
+
5906
+ const span = document.createElement('span');
5907
+ span.textContent = opt.label;
5908
+
5909
+ label.appendChild(checkbox);
5910
+ label.appendChild(span);
5911
+ optionDiv.appendChild(label);
5912
+ this._panel.appendChild(optionDiv);
5912
5913
  });
5913
- } catch (error) {
5914
- console.error('RT_FitBoundsMapbox: 拟合地图边界时出错', error);
5914
+
5915
+ // 创建按钮组:取消 / 确定
5916
+ const buttonGroup = document.createElement('div');
5917
+ buttonGroup.style.marginTop = '12px';
5918
+ buttonGroup.style.display = 'flex';
5919
+ buttonGroup.style.gap = '8px';
5920
+ buttonGroup.style.justifyContent = 'flex-end';
5921
+
5922
+ const cancelButton = document.createElement('button');
5923
+ cancelButton.textContent = '取消';
5924
+ cancelButton.style.cssText = `
5925
+ padding: 6px 12px;
5926
+ background: #ccc;
5927
+ border: none;
5928
+ border-radius: 4px;
5929
+ cursor: pointer;
5930
+ width: auto;
5931
+ `;
5932
+ cancelButton.addEventListener('click', () => this._handleCancel());
5933
+
5934
+ const confirmButton = document.createElement('button');
5935
+ confirmButton.textContent = '确定';
5936
+ confirmButton.style.cssText = `
5937
+ padding: 6px 12px;
5938
+ background: #007cba;
5939
+ color: white;
5940
+ border: none;
5941
+ border-radius: 4px;
5942
+ cursor: pointer;
5943
+ width: auto;
5944
+ `;
5945
+ confirmButton.addEventListener('click', () => this._handleConfirm());
5946
+
5947
+ buttonGroup.appendChild(cancelButton);
5948
+ buttonGroup.appendChild(confirmButton);
5949
+ this._panel.appendChild(buttonGroup);
5915
5950
  }
5916
5951
 
5917
- };
5918
- const RT_FitBoundsMapboxNative = (map, featureCollection) => {
5919
- try {
5920
- if (!map || !featureCollection) {
5921
- console.error('RT_FitBoundsMapbox: map 或 featureCollection 参数不能为空');
5922
- return;
5952
+ _getSelectedOption(value) {
5953
+ return this._selectedOptions?.includes(value) || false;
5954
+ }
5955
+
5956
+ _toggleOption(value) {
5957
+ if (!this._selectedOptions) this._selectedOptions = [];
5958
+
5959
+ const idx = this._selectedOptions.indexOf(value);
5960
+ if (idx > -1) {
5961
+ this._selectedOptions.splice(idx, 1);
5962
+ } else {
5963
+ this._selectedOptions.push(value);
5923
5964
  }
5924
5965
 
5925
- // 手写函数:计算 FeatureCollection 的 Bounding Box [minLng, minLat, maxLng, maxLat]
5926
- const calculateBoundingBox = (featureCollection) => {
5927
- let minLng = Infinity;
5928
- let minLat = Infinity;
5929
- let maxLng = -Infinity;
5930
- let maxLat = -Infinity;
5966
+ // 可选:重新渲染复选框状态(如果需要动态更新 UI,但目前 onchange 已处理)
5967
+ }
5931
5968
 
5932
- // 遍历每个 feature
5933
- for (const feature of featureCollection.features) {
5934
- const coords = feature.geometry.coordinates;
5969
+ _handleConfirm() {
5970
+ this._setExpanded(false);
5935
5971
 
5936
- // 递归遍历坐标(支持 Point、LineString、Polygon、Multi 等)
5937
- const traverseCoordinates = (coords) => {
5938
- if (!Array.isArray(coords)) return;
5972
+ this._onConfirm({
5973
+ selectedOptions: this._selectedOptions || [],
5974
+ unselectedOptions: this._options.filter((item) => !this._selectedOptions?.includes(item.value)),
5975
+ allOptions: this._options,
5976
+ });
5977
+ }
5939
5978
 
5940
- // 判断坐标类型:
5941
- if (typeof coords[0] === 'number') {
5942
- // 🟢 这是一个点 [lng, lat]
5943
- const [lng, lat] = coords;
5944
- if (lng < minLng) minLng = lng;
5945
- if (lng > maxLng) maxLng = lng;
5946
- if (lat < minLat) minLat = lat;
5947
- if (lat > maxLat) maxLat = lat;
5948
- } else if (Array.isArray(coords[0])) {
5949
- // 🔵 可能是 LineString、Polygon、MultiPoint 等(嵌套数组)
5950
- for (const subCoords of coords) {
5951
- traverseCoordinates(subCoords);
5952
- }
5953
- }
5954
- // 注意:更复杂的 GeometryCollection 或 MultiPolygon 也按类似方式递归,但此处已覆盖大部分情况
5955
- };
5979
+ _handleCancel() {
5980
+ this._setExpanded(false);
5981
+ }
5982
+
5983
+ onRemove() {
5984
+ if (this._container && this._container.parentNode) {
5985
+ this._container.parentNode.removeChild(this._container);
5986
+ }
5987
+ }
5988
+ }
5989
+
5990
+ // 自定义控件类:ToggleControl
5991
+ class CustomToggleControl {
5992
+ /**
5993
+ * 构造函数
5994
+ * @param {Object} options
5995
+ * @param {string} options.name - 控件名称(用于识别,可选展示)
5996
+ * @param {string} options.field - 控制的字段名,对应 map.SourceMap[field]
5997
+ * @param {boolean} options.defaultValue - 默认值(当 field 未定义时使用)
5998
+ * @param {string} options.svgIcon - SVG 图标字符串,例如 '<svg>...</svg>'
5999
+ * @param {Function} [options.onToggle] - 可选的回调函数,状态切换后调用,参数为最新的布尔值
6000
+ */
6001
+ constructor({ name, field, defaultValue = false, svgIcon, onToggle }) {
6002
+ this.name = name;
6003
+ this.field = field;
6004
+ this.defaultValue = defaultValue;
6005
+ this.svgIcon = svgIcon;
6006
+ this.onToggle = onToggle; // ✅ 新增:回调函数
5956
6007
 
5957
- traverseCoordinates(coords);
5958
- }
6008
+ // 控件容器
6009
+ this._container = null;
6010
+ this._button = null;
6011
+ this.isActive = false; // 当前是否为激活状态(高亮/true)
6012
+ }
5959
6013
 
5960
- // 如果没有有效的点(比如 featureCollection 为空或没有坐标)
5961
- if (
5962
- minLng === Infinity ||
5963
- minLat === Infinity ||
5964
- maxLng === -Infinity ||
5965
- maxLat === -Infinity
5966
- ) {
5967
- console.error('RT_FitBoundsMapbox: 未找到有效的坐标点');
5968
- return null;
5969
- }
6014
+ // Mapbox 要求的 onAdd 方法
6015
+ onAdd(map) {
6016
+ this.map = map;
5970
6017
 
5971
- return [minLng, minLat, maxLng, maxLat];
5972
- };
6018
+ // 创建外层容器 div
6019
+ this._container = document.createElement('div');
6020
+ this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group';
5973
6021
 
5974
- // 调用手写函数,计算 bounding box
5975
- const boundsArray = calculateBoundingBox(featureCollection);
6022
+ // 创建 button 元素
6023
+ this._button = document.createElement('button');
6024
+ this._button.type = 'button';
6025
+ this._button.innerHTML = this.svgIcon;
5976
6026
 
5977
- if (!boundsArray) {
5978
- console.error('RT_FitBoundsMapbox: 无法计算有效的包围盒');
5979
- return;
5980
- }
6027
+ // 设置按钮样式
6028
+ this._button.style.cursor = 'pointer';
6029
+ this._button.style.border = 'none';
6030
+ this._button.style.background = 'none';
6031
+ this._button.style.padding = '0';
6032
+ this._button.style.display = 'flex';
6033
+ this._button.style.alignItems = 'center';
6034
+ this._button.style.justifyContent = 'center';
5981
6035
 
5982
- const [minLng, minLat, maxLng, maxLat] = boundsArray;
6036
+ // 初始化状态
6037
+ this.updateStatus();
5983
6038
 
5984
- const bounds = [
5985
- [minLng, minLat], // 左下角
5986
- [maxLng, maxLat] // 右上角
5987
- ];
5988
- console.log(`output->bounds`, bounds);
5989
- bounds.forEach(item => {
5990
- item.forEach((i, index) => {
5991
- item[index] = Number((i).toFixed(6));
5992
- });
5993
- });
5994
- // ✅ 调用 Mapbox 的 fitBounds
5995
- map.fitBounds(bounds, {
5996
- padding: 50, // 可选:边距(单位像素)
5997
- maxZoom: 15, // 可选:最大缩放级别,避免太近
5998
- duration: 1000, // 可选:动画时长(毫秒)
6039
+ // 绑定点击事件
6040
+ this._button.addEventListener('click', () => {
6041
+ this.toggle();
5999
6042
  });
6000
6043
 
6001
- } catch (error) {
6002
- console.error('RT_FitBoundsMapbox: 拟合地图边界时出错', error);
6044
+ this._container.appendChild(this._button);
6045
+
6046
+ return this._container;
6003
6047
  }
6004
- };
6005
-
6006
- var RainteeGISUtil = /*#__PURE__*/Object.freeze({
6007
- __proto__: null,
6008
- RT_FitBoundsMapbox: RT_FitBoundsMapbox,
6009
- RT_FitBoundsMapboxNative: RT_FitBoundsMapboxNative,
6010
- getFeatureOutsidePart: getFeatureOutsidePart
6011
- });
6012
-
6013
- const treeDataAdapter = (data) => {
6014
- let i = 0;
6015
- let treeData = [];
6016
- let checkedKeys = [];
6017
- data.forEach((item) => {
6018
- let kvs = item.id.split(';').map(kvstr => kvstr.split('='));
6019
- let groups = kvs.find(item => item[0] === 'Main')[1].split('/');
6020
- let layerName = kvs.find(item => item[0] === 'CN')[1];
6021
- let currentNode = treeData;
6022
- groups.forEach((group, index) => {
6023
- currentNode.find(item => item.label === group) || i++;
6024
- currentNode.find(item => item.label === group) || currentNode.push({
6025
- id: i,
6026
- label: group,
6027
- children: []
6028
- });
6029
- currentNode = currentNode.find(item => item.label === group).children;
6030
- });
6031
- i++;
6032
- currentNode.push({
6033
- id: i,
6034
- label: layerName,
6035
- fullLayerName: groups.length === 0 ? layerName : groups.join('-') + '-' + layerName,
6036
- layerId: item.id,
6037
- opacity: '1.0',
6038
- });
6039
- if (item.layout && item.layout.visibility == 'visible') {
6040
- checkedKeys.push(i);
6048
+
6049
+ // Mapbox 要求的 onRemove 方法
6050
+ onRemove() {
6051
+ if (this._container && this._container.parentNode) {
6052
+ this._container.parentNode.removeChild(this._container);
6041
6053
  }
6042
- });
6043
- return { treeData, checkedKeys }
6044
- };
6045
- const treeDataAdapterNext = (data) => {
6046
- let i = 0;
6047
- let treeData = [];
6048
- let checkedKeys = [];
6049
- data.forEach((item) => {
6050
- let kvs = item.id.split(';').map(kvstr => kvstr.split('='));
6051
- let groups = kvs.find(item => item[0] === 'Main')[1].split('/');
6052
- let layerName = kvs.find(item => item[0] === 'CN')[1];
6053
- let currentNode = treeData;
6054
- groups.length === 0 ? layerName : groups.join('-') + '-' + layerName;
6055
- groups.forEach((group, index) => {
6056
- currentNode.find(item => item.label === group) || i++;
6057
- currentNode.find(item => item.label === group) || currentNode.push({
6058
- id: `TreeId=${i};`,
6059
- label: group,
6060
- children: []
6061
- });
6062
- currentNode = currentNode.find(item => item.label === group).children;
6063
- });
6064
- i++;
6065
- currentNode.push({
6066
- id: item.id,
6067
- label: layerName,
6068
- fullLayerName: getLayerIdFidld(item.id, 'Main') + '-' + getLayerIdFidld(item.id, 'CN'),
6069
- layerId: item.id,
6070
- opacity: '1.0',
6071
- });
6072
- if (item.layout && item.layout.visibility == 'visible') {
6073
- checkedKeys.push(i);
6054
+ this.map = null;
6055
+ this._container = null;
6056
+ this._button = null;
6057
+ }
6058
+
6059
+ // 更新控件状态(根据当前 field 值)
6060
+ updateStatus() {
6061
+ // 从 map.SourceMap 中获取当前 field 的值,如果不存在则使用 defaultValue
6062
+ let currentValue = this.defaultValue;
6063
+
6064
+ if (
6065
+ this.map &&
6066
+ this.map.SourceMap &&
6067
+ typeof this.map.SourceMap[this.field] === 'boolean'
6068
+ ) {
6069
+ currentValue = this.map.SourceMap[this.field];
6074
6070
  }
6075
- });
6076
- return { treeData, checkedKeys }
6077
- };
6078
- const getLayerIdFidld = (str, field) => {
6079
- let kvs = str.split(';').map(kvstr => kvstr.split('='));
6080
- let layerId = kvs.find(item => item[0] === field)[1];
6081
- return layerId
6082
- };
6083
- /**
6084
- * 生成唯一ID字符串
6085
- * @param {string} projectId 项目ID
6086
- * @param {string} objectTypeId 对象种类ID
6087
- * @param {string} groupId 对象分组ID
6088
- * @param {string} objectId 对象ID
6089
- * @returns {string} 唯一ID字符串
6090
- */
6091
- const GenerateUniqueId = (
6092
- projectId,
6093
- objectTypeId,
6094
- groupId,
6095
- objectId
6096
- ) => {
6097
- // 将参数组合成一个字符串
6098
- const combinedString = `${projectId}|${objectTypeId}|${groupId}|${objectId}`;
6099
6071
 
6100
- // 使用简单的哈希函数生成固定长度的字符串
6101
- let hash = 0;
6102
- for (let i = 0; i < combinedString.length; i++) {
6103
- const char = combinedString.charCodeAt(i);
6104
- hash = (hash << 5) - hash + char;
6105
- hash = hash & hash; // Convert to 32bit integer
6072
+ this.isActive = currentValue; // true 表示激活,false 表示未激活
6073
+
6074
+ // 更新按钮样式
6075
+ if (this.isActive) {
6076
+ this._button.style.opacity = '1';
6077
+ this._button.style.filter = 'none';
6078
+ } else {
6079
+ this._button.style.opacity = '0.4';
6080
+ this._button.style.filter = 'grayscale(100%)';
6081
+ }
6106
6082
  }
6107
6083
 
6108
- // 转换为16进制字符串并补零到8位
6109
- const hexHash = (hash >>> 0).toString(16).padStart(8, "0");
6084
+ // 切换状态
6085
+ toggle() {
6086
+ // 获取当前值
6087
+ let currentValue = this.defaultValue;
6110
6088
 
6111
- // 添加前缀和分隔符增强可读性
6112
- return `UID-${projectId.slice(0, 2)}-${objectTypeId.slice(
6113
- 0,
6114
- 2
6115
- )}-${groupId.slice(0, 2)}-${hexHash}`;
6116
- };
6117
-
6118
- var RainteeSourceMapTool = /*#__PURE__*/Object.freeze({
6119
- __proto__: null,
6120
- GenerateUniqueId: GenerateUniqueId,
6121
- getLayerIdFidld: getLayerIdFidld,
6122
- treeDataAdapter: treeDataAdapter,
6123
- treeDataAdapterNext: treeDataAdapterNext
6124
- });
6089
+ if (
6090
+ this.map &&
6091
+ this.map.SourceMap &&
6092
+ typeof this.map.SourceMap[this.field] === 'boolean'
6093
+ ) {
6094
+ currentValue = this.map.SourceMap[this.field];
6095
+ }
6096
+
6097
+ // 切换值
6098
+ const newValue = !currentValue;
6099
+
6100
+ // 更新到 map.SourceMap 中
6101
+ if (this.map && this.map.SourceMap) {
6102
+ this.map.SourceMap[this.field] = newValue;
6103
+ }
6104
+
6105
+ // 更新内部状态
6106
+ this.isActive = newValue;
6107
+
6108
+ // 更新 UI 样式
6109
+ this.updateStatus();
6110
+
6111
+ // ✅ 调用回调函数(如果传入了 onToggle),并传入最新的值
6112
+ this.onToggle?.(newValue); // 安全调用,仅当函数存在时才执行
6113
+ }
6114
+ }
6125
6115
 
6126
6116
  // RasterLayerController.js
6127
6117
 
@@ -6382,12 +6372,7 @@ class RasterLayerControl {
6382
6372
  this._layerListContainer && this._layerListContainer.appendChild(document.createTextNode('无可选内容'));
6383
6373
  }
6384
6374
  }
6385
- }
6386
- const RasterLayerControlBuilder = {
6387
- build(args) {
6388
- return new RasterLayerControl(args);
6389
- }
6390
- };
6375
+ }
6391
6376
 
6392
6377
  // terrain-toggle-control.js
6393
6378
 
@@ -6514,10 +6499,29 @@ class TerrainToggleControl {
6514
6499
  this._map.off('styledata', _onStyleData);
6515
6500
  this._map = null;
6516
6501
  }
6517
- }
6518
- const TerrainToggleControlBuilder = {
6519
- build(args) {
6520
- return new TerrainToggleControl(args);
6502
+ }
6503
+
6504
+ const DrawCacheFeatureManager = {
6505
+ _SourceId: 'drawCache',
6506
+ _id: '',
6507
+ constructor: (options) => {
6508
+ if (options.SourceId) DrawCacheFeatureManager._SourceId = options.SourceId;
6509
+ },
6510
+ on: (map, id) => {
6511
+ if (!map.getSource(DrawCacheFeatureManager._SourceId)) return
6512
+ console.log(`output->map.loaded()`, map.loaded());
6513
+ DrawCacheFeatureManager._id && map.setFeatureState({ source: DrawCacheFeatureManager._SourceId, id: DrawCacheFeatureManager._id }, { hover: false });
6514
+ map.setFeatureState({ source: DrawCacheFeatureManager._SourceId, id: id }, { hover: true });
6515
+ DrawCacheFeatureManager._id = id;
6516
+ // console.log(`output->成功高亮ID`, parseInt(id))
6517
+
6518
+ },
6519
+ off: (map, id) => {
6520
+ if (!map.getSource(DrawCacheFeatureManager._SourceId)) return
6521
+ DrawCacheFeatureManager._id && map.setFeatureState({ source: DrawCacheFeatureManager._SourceId, id: DrawCacheFeatureManager._id }, { hover: false });
6522
+ id && map.setFeatureState({ source: DrawCacheFeatureManager._SourceId, id: id }, { hover: false });
6523
+ DrawCacheFeatureManager._id = '';
6524
+ // console.log(`output->成功恢复常态ID`, parseInt(id))
6521
6525
  }
6522
6526
  };
6523
6527
 
@@ -6582,5 +6586,5 @@ const useDrawCache = () => {
6582
6586
  }
6583
6587
  };
6584
6588
 
6585
- export { CustomOptionsControlBuilder, CustomToggleControlBuilder, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControlBuilder, TerrainToggleControlBuilder, useDrawCache };
6589
+ export { CustomOptionsControl, CustomToggleControl, DrawCacheFeatureManager, RainteeConstants, RainteeGISUtil, RainteeSourceMapTool, RasterLayerControl, TerrainToggleControl, useDrawCache };
6586
6590
  //# sourceMappingURL=index.js.map