基于SuperMap REST-地图服务的B/S端简易图层样式编辑器实现

基于SuperMap REST-地图服务的B/S端简易图层样式编辑器实现

一、项目概述

在GIS(地理信息系统)应用开发中,图层样式的动态修改是一个常见需求。本文介绍一个基于 SuperMap iClient Leaflet 开发的简易图层样式编辑器,支持多地图管理和图层样式的实时调整。

基于SuperMap REST-地图服务的前端图层样式编辑器

功能特点

  • 多地图管理:支持多个REST地图服务的加载和管理
  • 图层可见性控制:通过复选框切换图层显示/隐藏
  • 图层顺序调整:拖拽排序改变图层叠加顺序
  • 样式实时修改:支持修改图层填充颜色和透明度
  • REST API交互:通过SuperMap REST API实现样式持久化

二、技术栈

技术 版本 说明
SuperMap iClient Leaflet 最新 GIS地图组件库
Leaflet 1.9.4 开源地图库
HTML5 - 页面结构
CSS3 - 样式设计
JavaScript ES6+ 业务逻辑

三、核心功能实现

3.1 项目结构

复制代码
├── 多地图管理与图层样式管理.html    # 主页面
└── 依赖资源(CDN加载)
    ├── leaflet.js
    ├── iclient-leaflet-es6.min.js
    └── leaflet.css

3.2 地图初始化

javascript 复制代码
var map;
var mapLayers = [];
var layerStyles = {};
var allLayersData = {};

var mapConfigs = [
  { id: 'base', name: '成都市底图', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市底图', visible: true },
  { id: 'main', name: '成都市主城区', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市主城区', visible: true },
  { id: 'shop', name: '成都市商店', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市商店', visible: true }
];

function initMap() {
  new L.supermap.MapService(mapConfigs[0].url).getMapInfo().then((res) => {
    map = L.map('map', {
      crs: L.CRS.EPSG4326,
      center: [30.76, 103.94],
      maxZoom: 18,
      zoom: 12,
      wrapX: false
    });
    
    mapConfigs.forEach((config) => {
      addMapLayer(config);
    });
    
    renderMapList();
    loadLayerStyles();
  });
}

关键技术点

  • 使用 L.supermap.MapService 获取地图信息,确保坐标系正确
  • 通过 L.supermap.TiledMapLayer 创建瓦片图层
  • 批量加载多个地图服务

3.3 图层管理面板

javascript 复制代码
function renderMapList() {
  var list = document.getElementById('mapList');
  list.innerHTML = '';
  
  mapLayers.forEach((item, index) => {
    var li = document.createElement('li');
    li.className = 'map-item';
    li.setAttribute('data-index', index);
    li.setAttribute('draggable', 'true');
    
    li.innerHTML = `
      <input type="checkbox" ${item.config.visible ? 'checked' : ''} data-index="${index}">
      <span class="map-name">${item.config.name}</span>
      <span class="drag-handle">⋮⋮</span>
    `;
    
    li.addEventListener('dragstart', handleDragStart);
    li.addEventListener('dragover', handleDragOver);
    li.addEventListener('drop', handleDrop);
    li.addEventListener('dragend', handleDragEnd);
    
    var checkbox = li.querySelector('input[type="checkbox"]');
    checkbox.addEventListener('change', function(e) {
      e.stopPropagation();
      toggleLayerVisibility(index);
    });
    
    list.appendChild(li);
  });
}

核心功能

  • 动态渲染图层列表
  • 支持拖拽排序
  • 复选框控制图层可见性

3.4 拖拽排序实现

javascript 复制代码
var draggedIndex = -1;

function handleDrop(e) {
  e.preventDefault();
  var dropIndex = parseInt(e.target.getAttribute('data-index'));
  
  if (draggedIndex !== -1 && draggedIndex !== dropIndex) {
    var draggedItem = mapLayers[draggedIndex];
    mapLayers.splice(draggedIndex, 1);
    mapLayers.splice(dropIndex, 0, draggedItem);
    
    reorderLayers();
    renderMapList();
  }
  
  document.querySelectorAll('.map-item').forEach(item => item.classList.remove('drag-over'));
}

function reorderLayers() {
  mapLayers.forEach(item => {
    if (item.config.visible) {
      map.removeLayer(item.layer);
    }
  });
  
  mapLayers.forEach(item => {
    if (item.config.visible) {
      item.layer.addTo(map);
    }
  });
}

实现原理

  • 使用HTML5拖拽API实现列表排序
  • 通过移除再添加图层实现顺序调整

3.5 图层样式读取

javascript 复制代码
function loadLayerStyles() {
  var mapSelect = document.getElementById('mapSelect');
  mapSelect.innerHTML = '<option value="">请选择地图</option>';
  
  mapConfigs.forEach((config) => {
    var option = document.createElement('option');
    option.value = config.name;
    option.textContent = config.name;
    mapSelect.appendChild(option);
  });
  
  mapConfigs.forEach((config) => {
    var url = config.url + '/layers.json';
    fetch(url)
      .then(response => response.json())
      .then(data => {
        allLayersData[config.name] = data;
      })
      .catch(error => {
        console.error('加载图层样式失败:', error);
      });
  });
}

技术要点

  • 调用SuperMap REST API获取图层配置
  • 缓存图层数据到 allLayersData 对象

3.6 颜色格式转换

javascript 复制代码
function rgbaToHex(rgba) {
  var r = Math.round(rgba.red).toString(16).padStart(2, '0');
  var g = Math.round(rgba.green).toString(16).padStart(2, '0');
  var b = Math.round(rgba.blue).toString(16).padStart(2, '0');
  return '#' + r + g + b;
}

function hexToRgba(hex, alpha) {
  var r = parseInt(hex.slice(1, 3), 16);
  var g = parseInt(hex.slice(3, 5), 16);
  var b = parseInt(hex.slice(5, 7), 16);
  return { red: r, green: g, blue: b, alpha: Math.round(alpha * 255) };
}

转换逻辑

  • SuperMap使用RGBA对象格式({red, green, blue, alpha}
  • HTML颜色选择器使用十六进制格式(#RRGGBB
  • 需要双向转换适配不同格式

3.7 样式更新API

javascript 复制代码
function updateLayerStyle(mapName, layerName, color) {
  var url = 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/' + encodeURIComponent(mapName) + '/layers.json';
  
  var updatedLayersData = JSON.parse(JSON.stringify(allLayersData[mapName]));
  
  updateLayerColorRecursively(updatedLayersData, layerName, color);
  
  fetch(url, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json;charset=UTF-8'
    },
    body: JSON.stringify(updatedLayersData)
  })
  .then(response => {
    if (response.ok) {
      return response.json();
    }
    throw new Error('更新失败');
  })
  .then(data => {
    allLayersData[mapName] = updatedLayersData;
    showStatus('样式更新成功!页面即将刷新...', 'success');
    setTimeout(function() {
      location.reload();
    }, 1000);
  })
  .catch(error => {
    showStatus('更新失败: ' + error.message, 'error');
    console.error('更新图层样式失败:', error);
  });
}

REST API调用流程

  1. 构造图层配置更新URL
  2. 深拷贝图层数据避免污染原数据
  3. 递归更新目标图层颜色
  4. 使用PUT请求提交更新
  5. 成功后刷新页面加载新样式

核心实现逻辑

3.8 递归更新图层颜色

javascript 复制代码
function updateLayerColorRecursively(layers, layerName, color) {
  if (Array.isArray(layers)) {
    layers.forEach(layer => {
      updateLayerColorRecursively(layer, layerName, color);
    });
  } else if (typeof layers === 'object' && layers !== null) {
    if (layers.name === layerName && layers.style) {
      layers.style.fillForeColor = color;
    }
    
    if (layers.subLayers && layers.subLayers.layers) {
      updateLayerColorRecursively(layers.subLayers.layers, layerName, color);
    }
  }
}

递归逻辑

  • 支持图层组嵌套结构
  • 匹配图层名称更新样式
  • 递归处理子图层

四、界面设计

4.1 布局结构

html 复制代码
<div id="controlPanel">
  <div class="tab-container">
    <div class="tab active" data-tab="mapManager">地图管理</div>
    <div class="tab" data-tab="styleManager">样式管理</div>
  </div>
  
  <div id="mapManager" class="tab-content active">
    <!-- 图层列表 -->
  </div>
  
  <div id="styleManager" class="tab-content">
    <!-- 样式编辑控件 -->
  </div>
</div>

4.2 样式设计

css 复制代码
#controlPanel { 
  position: absolute; 
  top: 20px; 
  right: 20px; 
  width: 320px; 
  background: rgba(255, 255, 255, 0.95); 
  border-radius: 10px; 
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); 
}

.tab { 
  flex: 1; 
  padding: 10px; 
  text-align: center; 
  font-size: 13px; 
  font-weight: bold; 
  color: #666; 
  cursor: pointer; 
  border-bottom: 2px solid transparent; 
}

.tab.active { 
  color: #1E9FFF; 
  border-bottom-color: #1E9FFF; 
}

设计特点

  • 半透明背景增强地图可见性
  • 卡片式设计配合阴影效果
  • 蓝色主题(#1E9FFF)统一风格
  • 响应式交互反馈

五、使用说明

5.1 环境要求

  1. SuperMap iServer:部署地图服务
  2. 服务地址http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/
  3. CORS配置:确保服务允许跨域请求

5.2 使用前提

在使用本工具前,需确保iServer服务端配置正确:

  1. 关闭缓存功能:在iServer服务管理界面中,关闭对应地图服务的"缓存"功能,确保更新的样式可以及时刷新生效

  2. 开启可编辑能力:在iServer服务管理界面中,开启对应地图服务的"可编辑"能力,确保PUT请求可以正常响应

5.3 操作步骤

  1. 地图管理

    • 勾选/取消勾选控制图层显示
    • 拖拽调整图层叠加顺序
  2. 样式管理

    • 选择目标地图
    • 选择目标图层
    • 调整填充颜色(颜色选择器)
    • 调整透明度(滑动条)
    • 点击"更新样式"按钮保存

六、注意事项

  1. 跨域问题:需在iServer中配置CORS或使用代理
  2. 服务权限:确保REST服务有写入权限
  3. 图层类型:当前仅支持矢量图层样式修改
  4. 样式属性 :当前仅修改 fillForeColor 属性,可扩展支持其他样式属性

七、扩展方向

  1. 更多样式属性:支持边框颜色、线条宽度、符号样式等
  2. 批量操作:支持多选图层批量修改样式
  3. 样式模板:预设样式方案快速应用
  4. 历史记录:支持样式修改回滚
  5. 图层过滤:按图层类型/名称筛选

八、完整代码

完整代码

html 复制代码
<!--********************************************************************
* Copyright© 2000 - 2025 SuperMap Software Co.Ltd. All rights reserved.
*********************************************************************-->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8" />
    <title>多地图管理与图层样式管理</title>
    <style>
      body { margin: 0; overflow: hidden; background: #fff; width: 100%; height: 100%; position: absolute; top: 0; font-family: 'Microsoft YaHei', sans-serif; }
      #map { margin: 0 auto; width: 100%; height: 100%; }
      #controlPanel { position: absolute; top: 20px; right: 20px; width: 320px; background: rgba(255, 255, 255, 0.95); border-radius: 10px; box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); padding: 15px; z-index: 1000; }
      .panel-header { font-weight: bold; font-size: 14px; color: #333; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #eee; display: flex; align-items: center; gap: 8px; }
      .panel-header::before { content: ''; width: 4px; height: 16px; background: #1E9FFF; border-radius: 2px; }
      .map-list { list-style: none; padding: 0; margin: 0; max-height: 150px; overflow-y: auto; }
      .map-item { display: flex; align-items: center; padding: 10px; margin-bottom: 6px; background: #f8f9fa; border-radius: 6px; cursor: move; transition: all 0.2s; }
      .map-item:hover { background: #e9ecef; }
      .map-item.dragging { opacity: 0.5; }
      .map-item.drag-over { border: 2px dashed #1E9FFF; }
      .map-item input[type="checkbox"] { margin-right: 10px; width: 16px; height: 16px; cursor: pointer; }
      .map-item .map-name { flex: 1; font-size: 13px; color: #333; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
      .map-item .drag-handle { width: 20px; height: 20px; display: flex; align-items: center; justify-content: center; color: #999; cursor: grab; }
      .map-item .drag-handle:active { cursor: grabbing; }
      .style-panel { margin-top: 15px; }
      .color-picker-container { display: flex; align-items: center; gap: 10px; margin-bottom: 15px; }
      .color-label { font-size: 13px; color: #666; white-space: nowrap; }
      #colorPicker { width: 50px; height: 40px; border: none; border-radius: 6px; cursor: pointer; padding: 0; }
      .opacity-container { margin-bottom: 15px; }
      .opacity-label { font-size: 13px; color: #666; margin-bottom: 8px; display: block; }
      #opacitySlider { width: 100%; height: 6px; -webkit-appearance: none; appearance: none; background: #ddd; border-radius: 3px; outline: none; }
      #opacitySlider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 18px; height: 18px; background: #1E9FFF; border-radius: 50%; cursor: pointer; transition: transform 0.2s; }
      #opacitySlider::-webkit-slider-thumb:hover { transform: scale(1.2); }
      #opacityValue { font-size: 13px; color: #1E9FFF; font-weight: bold; }
      .layer-select-container { margin-bottom: 15px; }
      #layerSelect { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 6px; font-size: 13px; background: white; cursor: pointer; }
      #updateBtn { width: 100%; padding: 10px; background: linear-gradient(135deg, #1E9FFF, #007BFF); color: white; border: none; border-radius: 6px; font-size: 14px; font-weight: bold; cursor: pointer; transition: all 0.3s; }
      #updateBtn:hover { transform: translateY(-1px); box-shadow: 0 4px 12px rgba(30, 159, 255, 0.4); }
      #updateBtn:active { transform: translateY(0); }
      .status-message { margin-top: 10px; padding: 10px; border-radius: 6px; font-size: 12px; display: none; }
      .status-success { background: #d4edda; color: #155724; }
      .status-error { background: #f8d7da; color: #721c24; }
      .tab-container { display: flex; margin-bottom: 15px; border-bottom: 1px solid #eee; }
      .tab { flex: 1; padding: 10px; text-align: center; font-size: 13px; font-weight: bold; color: #666; cursor: pointer; transition: all 0.2s; border-bottom: 2px solid transparent; }
      .tab.active { color: #1E9FFF; border-bottom-color: #1E9FFF; }
      .tab-content { display: none; }
      .tab-content.active { display: block; }
    </style>
  </head>
  <body>
    <div id="map" style="margin: 0 auto; width: 100%; height: 100%"></div>
    
    <div id="controlPanel">
      <div class="tab-container">
        <div class="tab active" data-tab="mapManager">地图管理</div>
        <div class="tab" data-tab="styleManager">样式管理</div>
      </div>
      
      <div id="mapManager" class="tab-content active">
        <div class="panel-header">图层列表</div>
        <ul id="mapList" class="map-list"></ul>
      </div>
      
      <div id="styleManager" class="tab-content">
        <div class="panel-header">图层样式</div>
        <div class="layer-select-container">
          <select id="mapSelect">
            <option value="">请选择地图</option>
          </select>
        </div>
        <div class="layer-select-container">
          <select id="layerSelect">
            <option value="">请选择图层</option>
          </select>
        </div>
        <div class="color-picker-container">
          <span class="color-label">填充颜色:</span>
          <input type="color" id="colorPicker" value="#D0FFF0">
        </div>
        <div class="opacity-container">
          <div class="opacity-label">透明度:<span id="opacityValue">100%</span></div>
          <input type="range" id="opacitySlider" min="0" max="100" value="100">
        </div>
        <button id="updateBtn">更新样式</button>
        <div id="statusMessage" class="status-message"></div>
      </div>
    </div>

    <script type="text/javascript" src="https://iclient.supermap.io/web/libs/leaflet/1.9.4/leaflet.js"></script>
    <script type="text/javascript" src="https://iclient.supermap.io/dist/leaflet/iclient-leaflet-es6.min.js"></script>
    <link rel="stylesheet" href="https://iclient.supermap.io/web/libs/leaflet/1.9.4/leaflet.css" />

    <script type="text/javascript">
      var map;
      var mapLayers = [];
      var layerStyles = {};
      var allLayersData = {};
      
      var mapConfigs = [
        { id: 'base', name: '成都市底图', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市底图', visible: true },
        { id: 'main', name: '成都市主城区', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市主城区', visible: true },
        { id: 'shop', name: '成都市商店', url: 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/成都市商店', visible: true }
      ];

      function initMap() {
        new L.supermap.MapService(mapConfigs[0].url).getMapInfo().then((res) => {
          map = L.map('map', {
            crs: L.CRS.EPSG4326,
            center: [30.76, 103.94],
            maxZoom: 18,
            zoom: 12,
            wrapX: false
          });
          
          mapConfigs.forEach((config) => {
            addMapLayer(config);
          });
          
          renderMapList();
          loadLayerStyles();
        });
      }

      function addMapLayer(config) {
        var layer = new L.supermap.TiledMapLayer(config.url);
        if (config.visible) {
          layer.addTo(map);
        }
        mapLayers.push({ config: config, layer: layer });
      }

      function renderMapList() {
        var list = document.getElementById('mapList');
        list.innerHTML = '';
        
        mapLayers.forEach((item, index) => {
          var li = document.createElement('li');
          li.className = 'map-item';
          li.setAttribute('data-index', index);
          li.setAttribute('draggable', 'true');
          
          li.innerHTML = `
            <input type="checkbox" ${item.config.visible ? 'checked' : ''} data-index="${index}">
            <span class="map-name">${item.config.name}</span>
            <span class="drag-handle">⋮⋮</span>
          `;
          
          li.addEventListener('dragstart', handleDragStart);
          li.addEventListener('dragover', handleDragOver);
          li.addEventListener('drop', handleDrop);
          li.addEventListener('dragend', handleDragEnd);
          
          var checkbox = li.querySelector('input[type="checkbox"]');
          checkbox.addEventListener('change', function(e) {
            e.stopPropagation();
            toggleLayerVisibility(index);
          });
          
          list.appendChild(li);
        });
      }

      var draggedIndex = -1;

      function handleDragStart(e) {
        draggedIndex = parseInt(e.target.getAttribute('data-index'));
        e.target.classList.add('dragging');
        e.dataTransfer.effectAllowed = 'move';
      }

      function handleDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';
        var items = document.querySelectorAll('.map-item');
        items.forEach(item => item.classList.remove('drag-over'));
        if (e.target.classList.contains('map-item')) {
          e.target.classList.add('drag-over');
        }
      }

      function handleDrop(e) {
        e.preventDefault();
        var dropIndex = parseInt(e.target.getAttribute('data-index'));
        
        if (draggedIndex !== -1 && draggedIndex !== dropIndex) {
          var draggedItem = mapLayers[draggedIndex];
          mapLayers.splice(draggedIndex, 1);
          mapLayers.splice(dropIndex, 0, draggedItem);
          
          reorderLayers();
          renderMapList();
        }
        
        document.querySelectorAll('.map-item').forEach(item => item.classList.remove('drag-over'));
      }

      function handleDragEnd() {
        draggedIndex = -1;
        document.querySelectorAll('.map-item').forEach(item => {
          item.classList.remove('dragging');
          item.classList.remove('drag-over');
        });
      }

      function toggleLayerVisibility(index) {
        var item = mapLayers[index];
        item.config.visible = !item.config.visible;
        
        if (item.config.visible) {
          item.layer.addTo(map);
        } else {
          map.removeLayer(item.layer);
        }
      }

      function reorderLayers() {
        mapLayers.forEach(item => {
          if (item.config.visible) {
            map.removeLayer(item.layer);
          }
        });
        
        mapLayers.forEach(item => {
          if (item.config.visible) {
            item.layer.addTo(map);
          }
        });
      }

      function loadLayerStyles() {
        var mapSelect = document.getElementById('mapSelect');
        mapSelect.innerHTML = '<option value="">请选择地图</option>';
        
        mapConfigs.forEach((config) => {
          var option = document.createElement('option');
          option.value = config.name;
          option.textContent = config.name;
          mapSelect.appendChild(option);
        });
        
        mapConfigs.forEach((config) => {
          var url = config.url + '/layers.json';
          fetch(url)
            .then(response => response.json())
            .then(data => {
              allLayersData[config.name] = data;
            })
            .catch(error => {
              console.error('加载图层样式失败:', error);
            });
        });
      }

      function parseLayerStyles(mapName) {
        var select = document.getElementById('layerSelect');
        select.innerHTML = '<option value="">请选择图层</option>';
        
        if (!mapName || !allLayersData[mapName]) {
          return;
        }
        
        var data = allLayersData[mapName];
        layerStyles = {};
        
        function extractLayers(layers) {
          if (layers.layers) {
            layers.layers.forEach(layer => {
              if (layer.name && layer.style) {
                layerStyles[layer.name] = layer;
                
                var option = document.createElement('option');
                option.value = layer.name;
                option.textContent = layer.caption || layer.name;
                select.appendChild(option);
                
                if (layer.style.fillForeColor) {
                  var color = rgbaToHex(layer.style.fillForeColor);
                  option.style.color = color;
                }
              }
              if (layer.subLayers && layer.subLayers.layers) {
                extractLayers(layer.subLayers);
              }
            });
          }
        }
        
        extractLayers({ layers: data });
      }

      function rgbaToHex(rgba) {
        var r = Math.round(rgba.red).toString(16).padStart(2, '0');
        var g = Math.round(rgba.green).toString(16).padStart(2, '0');
        var b = Math.round(rgba.blue).toString(16).padStart(2, '0');
        return '#' + r + g + b;
      }

      function hexToRgba(hex, alpha) {
        var r = parseInt(hex.slice(1, 3), 16);
        var g = parseInt(hex.slice(3, 5), 16);
        var b = parseInt(hex.slice(5, 7), 16);
        return { red: r, green: g, blue: b, alpha: Math.round(alpha * 255) };
      }

      document.getElementById('mapSelect').addEventListener('change', function(e) {
        var mapName = e.target.value;
        parseLayerStyles(mapName);
      });

      document.getElementById('layerSelect').addEventListener('change', function(e) {
        var layerName = e.target.value;
        if (layerName && layerStyles[layerName]) {
          var style = layerStyles[layerName];
          if (style.fillForeColor) {
            var color = rgbaToHex(style.fillForeColor);
            document.getElementById('colorPicker').value = color;
            var opacity = (style.fillForeColor.alpha / 255 * 100).toFixed(0);
            document.getElementById('opacitySlider').value = opacity;
            document.getElementById('opacityValue').textContent = opacity + '%';
          }
        }
      });

      document.getElementById('opacitySlider').addEventListener('input', function(e) {
        document.getElementById('opacityValue').textContent = e.target.value + '%';
      });

      document.getElementById('updateBtn').addEventListener('click', function() {
        var mapName = document.getElementById('mapSelect').value;
        var layerName = document.getElementById('layerSelect').value;
        var color = document.getElementById('colorPicker').value;
        var opacity = parseInt(document.getElementById('opacitySlider').value) / 100;
        
        if (!mapName) {
          showStatus('请先选择地图', 'error');
          return;
        }
        
        if (!layerName) {
          showStatus('请先选择图层', 'error');
          return;
        }
        
        var rgbaColor = hexToRgba(color, opacity);
        
        updateLayerStyle(mapName, layerName, rgbaColor);
      });

      function updateLayerStyle(mapName, layerName, color) {
        var url = 'http://localhost:8090/iserver/services/map-ChengduFresh/rest/maps/' + encodeURIComponent(mapName) + '/layers.json';
        
        var updatedLayersData = JSON.parse(JSON.stringify(allLayersData[mapName]));
        
        updateLayerColorRecursively(updatedLayersData, layerName, color);
        
        fetch(url, {
          method: 'PUT',
          headers: {
            'Content-Type': 'application/json;charset=UTF-8'
          },
          body: JSON.stringify(updatedLayersData)
        })
        .then(response => {
          if (response.ok) {
            return response.json();
          }
          throw new Error('更新失败');
        })
        .then(data => {
          allLayersData[mapName] = updatedLayersData;
          showStatus('样式更新成功!页面即将刷新...', 'success');
          setTimeout(function() {
            location.reload();
          }, 1000);
        })
        .catch(error => {
          showStatus('更新失败: ' + error.message, 'error');
          console.error('更新图层样式失败:', error);
        });
      }

      function updateLayerColorRecursively(layers, layerName, color) {
        if (Array.isArray(layers)) {
          layers.forEach(layer => {
            updateLayerColorRecursively(layer, layerName, color);
          });
        } else if (typeof layers === 'object' && layers !== null) {
          if (layers.name === layerName && layers.style) {
            layers.style.fillForeColor = color;
          }
          
          if (layers.subLayers && layers.subLayers.layers) {
            updateLayerColorRecursively(layers.subLayers.layers, layerName, color);
          }
        }
      }

      function refreshMap(mapName) {
        mapLayers.forEach(item => {
          if (item.config.name === mapName) {
            map.removeLayer(item.layer);
            item.layer = new L.supermap.TiledMapLayer(item.config.url);
            item.layer.addTo(map);
          }
        });
      }

      function showStatus(message, type) {
        var statusEl = document.getElementById('statusMessage');
        statusEl.textContent = message;
        statusEl.className = 'status-message status-' + type;
        statusEl.style.display = 'block';
        
        setTimeout(() => {
          statusEl.style.display = 'none';
        }, 3000);
      }

      document.querySelectorAll('.tab').forEach(tab => {
        tab.addEventListener('click', function() {
          document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
          document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
          
          this.classList.add('active');
          var tabId = this.getAttribute('data-tab');
          document.getElementById(tabId).classList.add('active');
        });
      });

      initMap();
    </script>
  </body>
</html>

结语:本文介绍的图层样式修改器为B/S端GIS应用提供了轻量化的样式管理解决方案,基于SuperMap REST API实现了样式的实时读取和更新,具有良好的扩展性和易用性。

相关推荐
Strayer2 天前
地图上叠加自定义图片(CAD图纸或高精度局部地图等)
gis
Strayer2 天前
在地图上实现管网拓扑批量移动、旋转与缩放(参考图片的实现方式)
gis·webgl·数据可视化
Strayer2 天前
WebGL 地图上做精准编辑?这套分层方案搞定管网拖拽 / 连接
gis·webgl
丷丩4 天前
我正用AI Agent重构传统GIS 核心功能,说大白话做空间分析
人工智能·gis·geoai
丷丩6 天前
策略模式实战:GeoAI-UP中MVT发布器的可扩展架构设计
人工智能·架构·gis·策略模式·空间分析·geoai
WebGIS开发7 天前
地理学硕士转行GIS开发经历分享
gis·webgis·地理学
丷丩14 天前
为什么Geo-UP是一款可以直接用于交付的智能应用
人工智能·gis·空间分析·geoai
JinSu_16 天前
3DGS的GIS可视化:将ply文件切片成3dtiles
gis·gltf·3dtiles·3dgs·3d高斯·splat
liuccn17 天前
QGIS Server 插件开发指南
gis·空间数据