raintee-maputils 1.0.51 → 1.0.53

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
@@ -21429,10 +21429,308 @@ class CustomToggleControl {
21429
21429
 
21430
21430
  // RasterLayerController.js
21431
21431
 
21432
+ // class RasterLayerControl {
21433
+ // constructor() {
21434
+ // this._panel = null;
21435
+ // this._dialog = null;
21436
+ // this._layerListContainer = null;
21437
+ // this._isOpen = false;
21438
+ // this._tempLayers = [];
21439
+ // }
21440
+
21441
+ // onAdd(map) {
21442
+ // this._map = map;
21443
+
21444
+ // this._container = document.createElement('div');
21445
+ // this._container.className = 'mapboxgl-ctrl mapboxgl-ctrl-group mapboxgl-ctrl-raster-control';
21446
+
21447
+ // this._button = document.createElement('button');
21448
+ // this._button.type = 'button';
21449
+ // this._button.innerHTML = '底图:初始化中';
21450
+ // this._button.style.cssText = `
21451
+ // width: 100%;
21452
+ // padding: 6px 10px;
21453
+ // margin: 0;
21454
+ // font-size: 14px;
21455
+ // font-family: Arial, sans-serif;
21456
+ // background: #f8f9fa;
21457
+ // color: #333;
21458
+ // border: 1px solid #dee2e6;
21459
+ // border-radius: 4px;
21460
+ // cursor: pointer;
21461
+ // text-align: left;
21462
+ // white-space: nowrap;
21463
+ // box-sizing: border-box;
21464
+ // line-height: 1.4;
21465
+ // `;
21466
+
21467
+ // this._button.addEventListener('mouseenter', () => {
21468
+ // this._button.style.background = '#e9ecef';
21469
+ // });
21470
+ // this._button.addEventListener('mouseleave', () => {
21471
+ // this._button.style.background = '#f8f9fa';
21472
+ // });
21473
+
21474
+ // this._button.addEventListener('click', () => {
21475
+ // this._togglePanel();
21476
+ // });
21477
+
21478
+ // this._container.appendChild(this._button);
21479
+
21480
+ // this._map.on('idle', () => this._updateButtonText());
21481
+ // this._map.on('styledata', () => this._updateButtonText());
21482
+ // this._map.on('sourcedata', () => this._updateButtonText());
21483
+
21484
+ // this._updateButtonText();
21485
+
21486
+ // return this._container;
21487
+ // }
21488
+
21489
+ // onRemove() {
21490
+ // if (this._panel && this._panel.parentNode) {
21491
+ // this._panel.parentNode.removeChild(this._panel);
21492
+ // }
21493
+ // if (this._container && this._container.parentNode) {
21494
+ // this._container.parentNode.removeChild(this._container);
21495
+ // }
21496
+ // this._map = undefined;
21497
+ // }
21498
+
21499
+ // _togglePanel() {
21500
+ // this._isOpen ? this._closePanel() : this._openPanel();
21501
+ // }
21502
+
21503
+ // _openPanel() {
21504
+ // this._createPanelIfNeeded();
21505
+
21506
+ // const layers = this._map.getStyle().layers || [];
21507
+ // const rasterLayers = layers.filter(l => l.type === 'raster');
21508
+
21509
+ // this._tempLayers = rasterLayers.map(l => ({
21510
+ // id: l.id,
21511
+ // visible: this._map.getLayoutProperty(l.id, 'visibility') !== 'none'
21512
+ // }));
21513
+
21514
+ // this._renderLayerList();
21515
+ // this._panel.style.display = 'flex';
21516
+ // this._isOpen = true;
21517
+ // }
21518
+
21519
+ // _closePanel() {
21520
+ // if (this._panel) {
21521
+ // this._panel.style.display = 'none';
21522
+ // }
21523
+ // this._isOpen = false;
21524
+ // }
21525
+
21526
+ // _createPanelIfNeeded() {
21527
+ // if (this._panel) return;
21528
+
21529
+ // // 遮罩
21530
+ // this._panel = document.createElement('div');
21531
+ // this._panel.style.cssText = `
21532
+ // position: fixed;
21533
+ // top: 0; left: 0;
21534
+ // width: 100vw; height: 100vh;
21535
+ // background: rgba(0,0,0,0.45);
21536
+ // display: none;
21537
+ // align-items: center;
21538
+ // justify-content: center;
21539
+ // z-index: 10000;
21540
+ // `;
21541
+
21542
+
21543
+ // // 对话框
21544
+ // this._dialog = document.createElement('div');
21545
+ // this._dialog.style.cssText = `
21546
+ // position: relative;
21547
+ // background: #fff;
21548
+ // padding: 16px;
21549
+ // border-radius: 10px; /* 稍微更柔和 */
21550
+ // min-width: 320px;
21551
+ // max-width: 520px;
21552
+ // max-height: 75vh; /* 🔥 再低一点,留呼吸感 */
21553
+ // overflow: hidden; /* 防止整体溢出 */
21554
+ // display: flex;
21555
+ // flex-direction: column;
21556
+ // box-shadow: 0 6px 18px rgba(0,0,0,.22);
21557
+ // font-family: Arial, sans-serif;
21558
+ // `;
21559
+
21560
+
21561
+
21562
+
21563
+ // // 标题
21564
+ // const title = document.createElement('div');
21565
+ // title.textContent = '🗂 管理栅格底图';
21566
+ // title.style.cssText = `
21567
+ // font-weight: bold;
21568
+ // margin-bottom: 8px;
21569
+ // font-size: 15px;
21570
+ // `;
21571
+
21572
+ // // 列表容器
21573
+ // this._layerListContainer = document.createElement('div');
21574
+ // this._layerListContainer.style.cssText = `
21575
+ // width: 100%;
21576
+ // max-height: 50vh; /* 🔥 核心:最多占屏幕一半高度 */
21577
+ // overflow-y: auto; /* 🔥 超出滚动 */
21578
+ // overflow-x: hidden;
21579
+ // display: flex;
21580
+ // flex-direction: column;
21581
+ // gap: 6px;
21582
+ // padding: 4px 2px;
21583
+ // box-sizing: border-box;
21584
+ // border-top: 1px solid #eee;
21585
+ // border-bottom: 1px solid #eee;
21586
+ // `;
21587
+
21588
+
21589
+
21590
+ // this._dialog.appendChild(title);
21591
+ // this._dialog.appendChild(this._layerListContainer);
21592
+ // this._panel.appendChild(this._dialog);
21593
+ // document.body.appendChild(this._panel);
21594
+
21595
+ // // 点击遮罩关闭
21596
+ // this._panel.addEventListener('click', (e) => {
21597
+ // if (e.target === this._panel) this._closePanel();
21598
+ // });
21599
+ // }
21600
+
21601
+ // _renderLayerList() {
21602
+ // if (!this._layerListContainer) return;
21603
+ // this._layerListContainer.innerHTML = '';
21604
+
21605
+ // if (!this._tempLayers.length) {
21606
+ // this._layerListContainer.textContent = '无可选栅格图层';
21607
+ // return;
21608
+ // }
21609
+
21610
+ // this._tempLayers.forEach((layer, index) => {
21611
+ // const row = document.createElement('div');
21612
+ // row.style.cssText = `
21613
+ // display: flex;
21614
+ // align-items: center;
21615
+ // gap: 6px;
21616
+ // padding: 6px 8px;
21617
+ // background: #f8f9fa;
21618
+ // border-radius: 4px;
21619
+ // `;
21620
+
21621
+ // const checkbox = document.createElement('input');
21622
+ // checkbox.type = 'checkbox';
21623
+ // checkbox.checked = layer.visible;
21624
+
21625
+ // checkbox.onchange = () => {
21626
+ // layer.visible = checkbox.checked;
21627
+
21628
+ // // 🔥 立刻在地图中生效
21629
+ // this._map.setLayoutProperty(
21630
+ // layer.id,
21631
+ // 'visibility',
21632
+ // layer.visible ? 'visible' : 'none'
21633
+ // );
21634
+
21635
+ // // 🔄 同步按钮文字
21636
+ // this._updateButtonText();
21637
+ // };
21638
+
21639
+
21640
+
21641
+ // const label = document.createElement('span');
21642
+ // label.textContent = layer.id;
21643
+ // label.style.cssText = `
21644
+ // flex: 1;
21645
+ // font-size: 13px;
21646
+ // color: #333;
21647
+ // overflow: hidden;
21648
+ // text-overflow: ellipsis;
21649
+ // white-space: nowrap;
21650
+ // `;
21651
+
21652
+ // const upBtn = document.createElement('button');
21653
+ // upBtn.textContent = '↑';
21654
+ // upBtn.disabled = index === 0;
21655
+ // upBtn.onclick = () => this._moveTempLayer(index, index - 1);
21656
+
21657
+ // const downBtn = document.createElement('button');
21658
+ // downBtn.textContent = '↓';
21659
+ // downBtn.disabled = index === this._tempLayers.length - 1;
21660
+ // downBtn.onclick = () => this._moveTempLayer(index, index + 1);
21661
+
21662
+ // [upBtn, downBtn].forEach(btn => {
21663
+ // btn.style.cssText = `
21664
+ // padding: 2px 6px;
21665
+ // border: 1px solid #ccc;
21666
+ // background: #fff;
21667
+ // border-radius: 3px;
21668
+ // cursor: pointer;
21669
+ // `;
21670
+ // });
21671
+
21672
+ // row.appendChild(checkbox);
21673
+ // row.appendChild(label);
21674
+ // row.appendChild(upBtn);
21675
+ // row.appendChild(downBtn);
21676
+ // this._layerListContainer.appendChild(row);
21677
+ // });
21678
+ // }
21679
+
21680
+ // _moveTempLayer(from, to) {
21681
+ // const item = this._tempLayers.splice(from, 1)[0];
21682
+ // this._tempLayers.splice(to, 0, item);
21683
+
21684
+ // // ✅ 按 Mapbox 官方语义:从“最上层”往下重排
21685
+ // for (let i = this._tempLayers.length - 1; i >= 0; i--) {
21686
+ // const layerId = this._tempLayers[i].id;
21687
+ // const beforeId =
21688
+ // i === this._tempLayers.length - 1
21689
+ // ? undefined // 最上面的图层:移到最顶
21690
+ // : this._tempLayers[i + 1].id;
21691
+
21692
+ // // 关键防御:避免 moveLayer 自己到自己前面
21693
+ // if (beforeId && beforeId === layerId) continue;
21694
+
21695
+ // this._map.moveLayer(layerId, beforeId);
21696
+ // }
21697
+
21698
+ // this._renderLayerList();
21699
+ // }
21700
+
21701
+
21702
+ // _applyChanges() {
21703
+ // // 现在不再做任何地图操作,只是关闭面板
21704
+ // this._closePanel();
21705
+ // }
21706
+
21707
+
21708
+ // _updateButtonText() {
21709
+ // if (!this._map) return;
21710
+
21711
+ // const layers = this._map.getStyle().layers || [];
21712
+ // const rasterLayers = layers.filter(l => l.type === 'raster');
21713
+
21714
+ // let firstVisible = null;
21715
+ // for (const layer of rasterLayers) {
21716
+ // if (this._map.getLayoutProperty(layer.id, 'visibility') === 'visible') {
21717
+ // firstVisible = layer.id;
21718
+ // break;
21719
+ // }
21720
+ // }
21721
+
21722
+ // this._button.innerHTML = firstVisible
21723
+ // ? `底图:${firstVisible}`
21724
+ // : rasterLayers.length ? '底图:无' : '底图:无栅格图层';
21725
+ // }
21726
+ // }
21432
21727
  class RasterLayerControl {
21433
21728
  constructor() {
21434
21729
  this._panel = null;
21730
+ this._dialog = null;
21731
+ this._layerListContainer = null;
21435
21732
  this._isOpen = false;
21733
+ this._tempLayers = [];
21436
21734
  }
21437
21735
 
21438
21736
  onAdd(map) {
@@ -21445,21 +21743,22 @@ class RasterLayerControl {
21445
21743
  this._button.type = 'button';
21446
21744
  this._button.innerHTML = '底图:初始化中';
21447
21745
  this._button.style.cssText = `
21448
- width: 100%;
21449
- padding: 6px 10px;
21450
- margin: 0;
21451
- font-size: 14px;
21452
- font-family: Arial, sans-serif;
21453
- background: #f8f9fa;
21454
- color: #333;
21455
- border: 1px solid #dee2e6;
21456
- border-radius: 4px;
21457
- cursor: pointer;
21458
- text-align: left;
21459
- white-space: nowrap;
21460
- box-sizing: border-box;
21461
- line-height: 1.4;
21462
- `;
21746
+ width: 100%;
21747
+ padding: 6px 10px;
21748
+ margin: 0;
21749
+ font-size: 14px;
21750
+ font-family: Arial, sans-serif;
21751
+ background: #f8f9fa;
21752
+ color: #333;
21753
+ border: 1px solid #dee2e6;
21754
+ border-radius: 4px;
21755
+ cursor: pointer;
21756
+ text-align: left;
21757
+ white-space: nowrap;
21758
+ box-sizing: border-box;
21759
+ line-height: 1.4;
21760
+ `;
21761
+
21463
21762
  this._button.addEventListener('mouseenter', () => {
21464
21763
  this._button.style.background = '#e9ecef';
21465
21764
  });
@@ -21467,26 +21766,17 @@ class RasterLayerControl {
21467
21766
  this._button.style.background = '#f8f9fa';
21468
21767
  });
21469
21768
 
21470
- this._container.appendChild(this._button);
21471
-
21472
21769
  this._button.addEventListener('click', () => {
21473
21770
  this._togglePanel();
21474
21771
  });
21475
21772
 
21476
- this._map.on('idle', () => {
21477
- this._updateRasterLayers();
21478
-
21479
- });
21480
-
21481
- this._map.on('styledata', () => {
21482
- this._updateRasterLayers();
21483
- });
21773
+ this._container.appendChild(this._button);
21484
21774
 
21485
- this._map.on('sourcedata', () => {
21486
- this._updateRasterLayers();
21487
- });
21775
+ this._map.on('idle', () => this._updateButtonText());
21776
+ this._map.on('styledata', () => this._updateButtonText());
21777
+ this._map.on('sourcedata', () => this._updateButtonText());
21488
21778
 
21489
- this._updateRasterLayers();
21779
+ this._updateButtonText();
21490
21780
 
21491
21781
  return this._container;
21492
21782
  }
@@ -21498,20 +21788,26 @@ class RasterLayerControl {
21498
21788
  if (this._container && this._container.parentNode) {
21499
21789
  this._container.parentNode.removeChild(this._container);
21500
21790
  }
21791
+ this._map = undefined;
21501
21792
  }
21502
21793
 
21503
21794
  _togglePanel() {
21504
- if (this._isOpen) {
21505
- this._closePanel();
21506
- } else {
21507
- this._openPanel();
21508
- }
21795
+ this._isOpen ? this._closePanel() : this._openPanel();
21509
21796
  }
21510
21797
 
21511
21798
  _openPanel() {
21512
21799
  this._createPanelIfNeeded();
21513
- this._updateRasterLayers();
21514
- this._panel.style.display = 'block';
21800
+
21801
+ const layers = this._map.getStyle().layers || [];
21802
+ const rasterLayers = layers.filter(l => l.type === 'raster');
21803
+
21804
+ this._tempLayers = rasterLayers.map(l => ({
21805
+ id: l.id,
21806
+ visible: this._map.getLayoutProperty(l.id, 'visibility') !== 'none'
21807
+ }));
21808
+
21809
+ this._renderLayerList();
21810
+ this._panel.style.display = 'flex';
21515
21811
  this._isOpen = true;
21516
21812
  }
21517
21813
 
@@ -21523,171 +21819,275 @@ class RasterLayerControl {
21523
21819
  }
21524
21820
 
21525
21821
  _createPanelIfNeeded() {
21526
- if (!this._panel) {
21527
- // 创建整体的弹窗容器(包含遮罩 + 弹窗内容)
21528
- this._panel = document.createElement('div');
21529
- this._panel.style.cssText = `
21530
- position: fixed;
21531
- top: 0;
21532
- left: 0;
21533
- width: 100vw;
21534
- height: 100vh;
21535
- background: rgba(0, 0, 0, 0.5);
21536
- display: flex;
21537
- align-items: center;
21538
- justify-content: center;
21539
- z-index: 10000;
21540
- `;
21541
-
21542
- // 创建实际的对话框(白色背景,居中内容区域)
21543
- this._dialog = document.createElement('div');
21544
- this._dialog.style.cssText = `
21545
- position: relative;
21546
- top: 50%;
21547
- left: 50%;
21548
- transform: translate(-50%, -50%);
21549
- display: flex;
21550
- flex-direction: column;
21551
- align-items: center;
21552
- justify-content: center;
21553
- background: white;
21554
- padding: 20px;
21555
- border-radius: 8px;
21556
- box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
21557
- min-width: 300px;
21558
- max-width: 500px;
21559
- max-height: 80vh;
21560
- overflow-y: auto;
21561
- font-family: Arial, sans-serif;
21562
- `;
21563
-
21564
- // 标题
21565
- const title = document.createElement('div');
21566
- title.textContent = '🗂 点击切换底图(影像栅格瓦片图层)';
21567
- title.style.fontWeight = 'bold';
21568
- title.style.marginBottom = '0.5rem';
21569
- title.style.fontSize = '1rem';
21570
- this._dialog.appendChild(title);
21571
-
21572
- // 图层列表容器(原 _layerListContainer)
21573
- this._layerListContainer = document.createElement('div');
21574
- this._layerListContainer.style.cssText = `
21575
- width: 100%;
21576
- max-height: 60vh;
21577
- display: flex;
21578
- flex-direction: column;
21579
- gap: 4px;
21580
- overflow-y: auto;
21581
- overflow-x: hidden;
21582
- `;
21583
- this._dialog.appendChild(this._layerListContainer);
21584
-
21585
- // // 关闭按钮(可选增强)
21586
- // const closeButton = document.createElement('button');
21587
- // closeButton.textContent = '✅ 关闭';
21588
- // closeButton.style.cssText = `
21589
- // margin-left: auto;
21590
- // margin-right: auto;
21591
- // margin-top: 15px;
21592
- // padding: 6px 12px;
21593
- // background: #6c757d;
21594
- // color: white;
21595
- // border: none;
21596
- // border-radius: 4px;
21597
- // cursor: pointer;
21598
- // `;
21599
- // closeButton.addEventListener('click', () => {
21600
- // this._closePanel();
21601
- // });
21602
- // this._dialog.appendChild(closeButton);
21603
-
21604
- this._panel.appendChild(this._dialog);
21605
- document.body.appendChild(this._panel);
21606
-
21607
- // 点击背景遮罩关闭弹窗(增强用户体验,可选)
21608
- this._panel.addEventListener('click', (e) => {
21609
- // 只有点击背景遮罩部分才关闭,点击对话框内容不关闭
21610
- if (e.target === this._panel) {
21611
- this._closePanel();
21612
- }
21613
- });
21614
- }
21615
- }
21616
-
21617
- _updateRasterLayers() {
21822
+ if (this._panel) return;
21823
+
21824
+ // 遮罩
21825
+ this._panel = document.createElement('div');
21826
+ this._panel.style.cssText = `
21827
+ position: fixed;
21828
+ top: 0; left: 0;
21829
+ width: 100vw; height: 100vh;
21830
+ background: rgba(0,0,0,0.45);
21831
+ display: none;
21832
+ align-items: center;
21833
+ justify-content: center;
21834
+ z-index: 10000;
21835
+ `;
21836
+
21837
+
21838
+ // 对话框
21839
+ this._dialog = document.createElement('div');
21840
+ this._dialog.style.cssText = `
21841
+ position: relative;
21842
+ background: #fff;
21843
+ padding: 16px;
21844
+ border-radius: 10px; /* 稍微更柔和 */
21845
+ min-width: 320px;
21846
+ max-width: 520px;
21847
+ max-height: 75vh; /* 🔥 再低一点,留呼吸感 */
21848
+ overflow: hidden; /* 防止整体溢出 */
21849
+ display: flex;
21850
+ flex-direction: column;
21851
+ box-shadow: 0 6px 18px rgba(0,0,0,.22);
21852
+ font-family: Arial, sans-serif;
21853
+ `;
21854
+
21855
+
21856
+
21857
+
21858
+ // 标题
21859
+ const title = document.createElement('div');
21860
+ title.textContent = '🗂 管理栅格底图';
21861
+ title.style.cssText = `
21862
+ font-weight: bold;
21863
+ margin-bottom: 8px;
21864
+ font-size: 15px;
21865
+ `;
21618
21866
 
21619
- const layers = this._map.getStyle().layers || [];
21620
- const rasterLayers = layers.filter(layer => layer.type === 'raster');
21867
+ // 列表容器
21868
+ this._layerListContainer = document.createElement('div');
21869
+ this._layerListContainer.style.cssText = `
21870
+ width: 100%;
21871
+ max-height: 50vh; /* 🔥 核心:最多占屏幕一半高度 */
21872
+ overflow-y: auto; /* 🔥 超出滚动 */
21873
+ overflow-x: hidden;
21874
+ display: flex;
21875
+ flex-direction: column;
21876
+ gap: 6px;
21877
+ padding: 4px 2px;
21878
+ box-sizing: border-box;
21879
+ border-top: 1px solid #eee;
21880
+ border-bottom: 1px solid #eee;
21881
+ `;
21882
+
21883
+
21884
+
21885
+ this._dialog.appendChild(title);
21886
+ this._dialog.appendChild(this._layerListContainer);
21887
+ this._panel.appendChild(this._dialog);
21888
+ document.body.appendChild(this._panel);
21621
21889
 
21622
- let buttonText = '底图:无';
21623
- let firstVisibleLayerId = null;
21890
+ // 点击遮罩关闭
21891
+ this._panel.addEventListener('click', (e) => {
21892
+ if (e.target === this._panel) this._closePanel();
21893
+ });
21894
+ }
21624
21895
 
21625
- // 查找当前显示的图层(visibility === 'visible')
21626
- for (const layer of rasterLayers) {
21627
- const visibility = this._map.getLayoutProperty(layer.id, 'visibility');
21628
- if (visibility === 'visible') {
21629
- firstVisibleLayerId = layer.id;
21630
- break;
21631
- }
21632
- }
21896
+ _renderLayerList() {
21897
+ if (!this._layerListContainer) return;
21898
+ this._layerListContainer.innerHTML = '';
21633
21899
 
21634
- if (firstVisibleLayerId) {
21635
- buttonText = `底图:${firstVisibleLayerId}`;
21636
- } else if (rasterLayers.length > 0) {
21637
- buttonText = '底图:无';
21900
+ if (!this._tempLayers.length) {
21901
+ this._layerListContainer.textContent = '无可选栅格图层';
21902
+ return;
21638
21903
  }
21639
21904
 
21640
- this._button.innerHTML = buttonText;
21641
-
21642
- // 清空旧的图层列表
21643
- if (this._layerListContainer) { this._layerListContainer.innerHTML = ''; }
21644
- rasterLayers.forEach(layer => {
21645
- const layerId = layer.id;
21905
+ this._tempLayers.forEach((layer, index) => {
21646
21906
  const row = document.createElement('div');
21647
21907
  row.style.cssText = `
21648
- padding: 6px 8px;
21649
- margin-bottom: 2px;
21650
- background: #f8f9fa;
21651
- border-radius: 4px;
21652
- cursor: pointer;
21653
- transition: background 0.2s;
21654
- `;
21908
+ display: flex;
21909
+ align-items: center;
21910
+ gap: 6px;
21911
+ padding: 6px 8px;
21912
+ background: #f8f9fa;
21913
+ border-radius: 4px;
21914
+ `;
21655
21915
 
21656
- row.addEventListener('click', () => {
21657
- // 设置当前图层为 visible,其他为 none
21658
- rasterLayers.forEach(l => {
21659
- const vis = l.id === layerId ? 'visible' : 'none';
21660
- this._map.setLayoutProperty(l.id, 'visibility', vis);
21661
- });
21916
+ const checkbox = document.createElement('input');
21917
+ checkbox.type = 'checkbox';
21918
+ checkbox.checked = layer.visible;
21919
+
21920
+ checkbox.onchange = () => {
21921
+ layer.visible = checkbox.checked;
21922
+
21923
+ // 🔥 立刻在地图中生效
21924
+ this._map.setLayoutProperty(
21925
+ layer.id,
21926
+ 'visibility',
21927
+ layer.visible ? 'visible' : 'none'
21928
+ );
21929
+
21930
+ // 🔄 同步按钮文字
21931
+ this._updateButtonText();
21932
+ };
21662
21933
 
21663
- // 更新按钮文字
21664
- if (this._button) {
21665
- this._button.innerHTML = `底图:${layerId}`;
21666
- }
21667
- this._togglePanel();
21668
- });
21669
21934
 
21670
- // 鼠标 hover 效果
21671
- row.addEventListener('mouseenter', () => {
21672
- row.style.background = '#e9ecef';
21673
- });
21674
- row.addEventListener('mouseleave', () => {
21675
- row.style.background = '#f8f9fa';
21676
- });
21677
21935
 
21678
- // 显示图层 ID(可加样式或 icon)
21679
21936
  const label = document.createElement('span');
21680
- label.textContent = layerId;
21681
- label.style.fontFamily = 'Arial, sans-serif';
21682
- label.style.fontSize = '13px';
21683
- label.style.color = '#333';
21937
+ label.textContent = layer.id;
21938
+ label.style.cssText = `
21939
+ flex: 1;
21940
+ font-size: 13px;
21941
+ color: #333;
21942
+ overflow: hidden;
21943
+ text-overflow: ellipsis;
21944
+ white-space: nowrap;
21945
+ `;
21946
+
21947
+ const upBtn = document.createElement('button');
21948
+ upBtn.textContent = '↑';
21949
+ upBtn.disabled = index === 0;
21950
+ upBtn.onclick = () => this._moveTempLayer(index, index - 1);
21951
+
21952
+ const downBtn = document.createElement('button');
21953
+ downBtn.textContent = '↓';
21954
+ downBtn.disabled = index === this._tempLayers.length - 1;
21955
+ downBtn.onclick = () => this._moveTempLayer(index, index + 1);
21956
+
21957
+ // 定位按钮
21958
+ const locateBtn = document.createElement('button');
21959
+ locateBtn.textContent = '⊕';
21960
+ locateBtn.title = '定位到该图层';
21961
+ let locateInfo = null;
21962
+ try {
21963
+ locateInfo = this._getLayerLocateInfo(layer.id);
21964
+ } catch (e) {
21965
+ console.warn('获取图层定位信息失败:', layer.id, e);
21966
+ }
21967
+ if (!locateInfo) {
21968
+ locateBtn.disabled = true;
21969
+ locateBtn.style.opacity = '0.3';
21970
+ }
21971
+ locateBtn.onclick = () => this._locateToLayer(layer.id, locateBtn);
21684
21972
 
21973
+ const btnStyle = `padding: 2px 6px; border: 1px solid #ccc; background: #fff; border-radius: 3px; cursor: pointer;`;
21974
+ upBtn.style.cssText = btnStyle;
21975
+ downBtn.style.cssText = btnStyle;
21976
+ locateBtn.style.cssText = locateInfo ? btnStyle : (btnStyle + ' opacity: 0.3; cursor: not-allowed;');
21977
+
21978
+ row.appendChild(checkbox);
21685
21979
  row.appendChild(label);
21686
- this._layerListContainer && this._layerListContainer.appendChild(row);
21980
+ row.appendChild(locateBtn);
21981
+ row.appendChild(upBtn);
21982
+ row.appendChild(downBtn);
21983
+ this._layerListContainer.appendChild(row);
21687
21984
  });
21688
- if (rasterLayers.length === 0) {
21689
- this._layerListContainer && this._layerListContainer.appendChild(document.createTextNode('无可选内容'));
21985
+ }
21986
+
21987
+ _moveTempLayer(from, to) {
21988
+ const item = this._tempLayers.splice(from, 1)[0];
21989
+ this._tempLayers.splice(to, 0, item);
21990
+
21991
+ // ✅ 按 Mapbox 官方语义:从“最上层”往下重排
21992
+ for (let i = this._tempLayers.length - 1; i >= 0; i--) {
21993
+ const layerId = this._tempLayers[i].id;
21994
+ const beforeId =
21995
+ i === this._tempLayers.length - 1
21996
+ ? undefined // 最上面的图层:移到最顶
21997
+ : this._tempLayers[i + 1].id;
21998
+
21999
+ // 关键防御:避免 moveLayer 自己到自己前面
22000
+ if (beforeId && beforeId === layerId) continue;
22001
+
22002
+ this._map.moveLayer(layerId, beforeId);
22003
+ }
22004
+
22005
+ this._renderLayerList();
22006
+ }
22007
+
22008
+
22009
+ _applyChanges() {
22010
+ // 现在不再做任何地图操作,只是关闭面板
22011
+ this._closePanel();
22012
+ }
22013
+
22014
+
22015
+ _updateButtonText() {
22016
+ if (!this._map) return;
22017
+
22018
+ const layers = this._map.getStyle().layers || [];
22019
+ const rasterLayers = layers.filter(l => l.type === 'raster');
22020
+
22021
+ let firstVisible = null;
22022
+ for (const layer of rasterLayers) {
22023
+ if (this._map.getLayoutProperty(layer.id, 'visibility') === 'visible') {
22024
+ firstVisible = layer.id;
22025
+ break;
22026
+ }
22027
+ }
22028
+
22029
+ this._button.innerHTML = firstVisible
22030
+ ? `底图:${firstVisible}`
22031
+ : rasterLayers.length ? '底图:无' : '底图:无栅格图层';
22032
+ }
22033
+
22034
+ _getLayerLocateInfo(layerId) {
22035
+ if (!this._map) return null;
22036
+ const style = this._map.getStyle();
22037
+ const layer = style.layers.find(l => l.id === layerId);
22038
+ if (!layer || !layer.source) return null;
22039
+ const source = style.sources[layer.source];
22040
+ if (!source || !source.tiles || !source.tiles.length) return null;
22041
+ const tileUrl = source.tiles[0];
22042
+ // 提取后端基础地址
22043
+ let baseUrl = '';
22044
+ try {
22045
+ const u = new URL(tileUrl, window.location.origin);
22046
+ baseUrl = u.origin;
22047
+ } catch (e) {
22048
+ baseUrl = '';
22049
+ }
22050
+ // 动态栅格
22051
+ const dynMatch = tileUrl.match(/\/raster\/dynamic\/tile\/([^/]+)\//);
22052
+ if (dynMatch) return { type: 'dynamic', name: dynMatch[1], baseUrl };
22053
+ // mbtiles栅格
22054
+ const staticMatch = tileUrl.match(/\/raster\/([^/]+)\//);
22055
+ if (staticMatch && !tileUrl.includes('/dynamic/')) return { type: 'static', name: staticMatch[1], baseUrl };
22056
+ return null;
22057
+ }
22058
+
22059
+ async _locateToLayer(layerId, btn) {
22060
+ const info = this._getLayerLocateInfo(layerId);
22061
+ if (!info) return;
22062
+ const origText = btn.textContent;
22063
+ btn.textContent = '...';
22064
+ btn.disabled = true;
22065
+ try {
22066
+ let url = '';
22067
+ if (info.type === 'static') {
22068
+ url = `${info.baseUrl}/raster/center/${info.name}`;
22069
+ } else {
22070
+ url = `${info.baseUrl}/raster/dynamic/center/${info.name}`;
22071
+ }
22072
+ const resp = await fetch(url);
22073
+ if (!resp.ok) throw new Error('请求失败');
22074
+ const data = await resp.json();
22075
+ if (data.x != null && data.y != null) {
22076
+ this._map.flyTo({
22077
+ center: [data.x, data.y],
22078
+ zoom: data.zoom || 10,
22079
+ duration: 1500
22080
+ });
22081
+ this._closePanel();
22082
+ }
22083
+ } catch (e) {
22084
+ console.error('定位失败:', e);
22085
+ btn.textContent = '✕';
22086
+ setTimeout(() => { btn.textContent = origText; btn.disabled = false; }, 1500);
22087
+ return;
21690
22088
  }
22089
+ btn.textContent = origText;
22090
+ btn.disabled = false;
21691
22091
  }
21692
22092
  }
21693
22093