OpenLayers 详细开发指南 - 第八部分 - GeoJSON 转换与处理工具

1. GeoJSON 数据处理工具

1.1 GeoJSON 转换与处理工具

javascript 复制代码
/**
 * 创建 GeoJSON 数据处理工具
 * 功能:将 GeoJSON 数据转换为 OpenLayers 要素,支持各种数据操作
 * 
 * @returns {Object} - 返回 GeoJSON 处理方法集合
 */
const createGeoJSONProcessor = () => {
  /**
   * GeoJSON 格式解析器
   * 用于 GeoJSON 与 OpenLayers 要素之间的相互转换
   */
  const geoJSONFormat = new GeoJSON();
  
  return {
    /**
     * 将 GeoJSON 对象转换为 OpenLayers 要素
     * @param {Object} geoJSON - GeoJSON 对象
     * @param {Object} options - 转换选项
     * @param {string} options.dataProjection - 数据坐标系(默认 EPSG:4326)
     * @param {string} options.featureProjection - 要素坐标系(默认 EPSG:3857)
     * @returns {Feature|Array<Feature>} OpenLayers 要素或要素数组
     */
    readGeoJSON(geoJSON, options = {}) {
      const readOptions = {
        dataProjection: options.dataProjection || 'EPSG:4326',
        featureProjection: options.featureProjection || 'EPSG:3857'
      };
      
      return geoJSONFormat.readFeatures(geoJSON, readOptions);
    },
    
    /**
     * 将 OpenLayers 要素转换为 GeoJSON 对象
     * @param {Feature|Array<Feature>} features - OpenLayers 要素或要素数组
     * @param {Object} options - 转换选项
     * @param {string} options.dataProjection - 输出坐标系(默认 EPSG:4326)
     * @param {string} options.featureProjection - 要素坐标系(默认 EPSG:3857)
     * @returns {Object} GeoJSON 对象
     */
    writeGeoJSON(features, options = {}) {
      const writeOptions = {
        dataProjection: options.dataProjection || 'EPSG:4326',
        featureProjection: options.featureProjection || 'EPSG:3857',
        rightHanded: options.rightHanded !== undefined ? options.rightHanded : true,
        decimals: options.decimals || 6
      };
      
      if (Array.isArray(features)) {
        return geoJSONFormat.writeFeaturesObject(features, writeOptions);
      } else {
        return geoJSONFormat.writeFeatureObject(features, writeOptions);
      }
    },
    
    /**
     * 从 URL 加载 GeoJSON 数据
     * @param {string} url - GeoJSON 数据地址
     * @param {Object} options - 加载选项
     * @returns {Promise<Array<Feature>>} 要素数组的 Promise
     */
    async loadFromURL(url, options = {}) {
      try {
        const response = await fetch(url);
        if (!response.ok) {
          throw new Error(`加载 GeoJSON 失败: ${response.status} ${response.statusText}`);
        }
        
        const geoJSON = await response.json();
        return this.readGeoJSON(geoJSON, options);
      } catch (error) {
        console.error('加载 GeoJSON 数据出错:', error);
        throw error;
      }
    },
    
    /**
     * 从字符串解析 GeoJSON 数据
     * @param {string} geoJSONString - GeoJSON 字符串
     * @param {Object} options - 解析选项
     * @returns {Feature|Array<Feature>} OpenLayers 要素或要素数组
     */
    parseFromString(geoJSONString, options = {}) {
      try {
        const geoJSON = JSON.parse(geoJSONString);
        return this.readGeoJSON(geoJSON, options);
      } catch (error) {
        console.error('解析 GeoJSON 字符串出错:', error);
        throw error;
      }
    },
    
    /**
     * 提取 GeoJSON 中的坐标数据
     * @param {Object} geoJSON - GeoJSON 对象
     * @returns {Array} 坐标数组
     */
    extractCoordinates(geoJSON) {
      const coordinates = [];
      
      // 提取不同类型几何图形的坐标
      const extractFromGeometry = (geometry) => {
        if (!geometry || !geometry.type || !geometry.coordinates) {
          return;
        }
        
        switch (geometry.type) {
          case 'Point':
            coordinates.push(geometry.coordinates);
            break;
          case 'LineString':
          case 'MultiPoint':
            coordinates.push(...geometry.coordinates);
            break;
          case 'Polygon':
          case 'MultiLineString':
            geometry.coordinates.forEach(line => {
              coordinates.push(...line);
            });
            break;
          case 'MultiPolygon':
            geometry.coordinates.forEach(polygon => {
              polygon.forEach(ring => {
                coordinates.push(...ring);
              });
            });
            break;
          case 'GeometryCollection':
            if (geometry.geometries) {
              geometry.geometries.forEach(geom => {
                extractFromGeometry(geom);
              });
            }
            break;
        }
      };
      
      // 处理不同类型的 GeoJSON
      if (geoJSON.type === 'Feature' && geoJSON.geometry) {
        extractFromGeometry(geoJSON.geometry);
      } else if (geoJSON.type === 'FeatureCollection' && geoJSON.features) {
        geoJSON.features.forEach(feature => {
          if (feature.geometry) {
            extractFromGeometry(feature.geometry);
          }
        });
      } else if (geoJSON.type && geoJSON.coordinates) {
        // 直接是几何对象
        extractFromGeometry(geoJSON);
      }
      
      return coordinates;
    },
    
    /**
     * 合并多个 GeoJSON 对象
     * @param {Array<Object>} geoJSONArray - GeoJSON 对象数组
     * @returns {Object} 合并后的 GeoJSON 对象
     */
    mergeGeoJSON(geoJSONArray) {
      const mergedFeatures = [];
      
      geoJSONArray.forEach(geoJSON => {
        // 处理 FeatureCollection
        if (geoJSON.type === 'FeatureCollection' && geoJSON.features) {
          mergedFeatures.push(...geoJSON.features);
        }
        // 处理单个 Feature
        else if (geoJSON.type === 'Feature') {
          mergedFeatures.push(geoJSON);
        }
        // 处理几何对象,将其转换为 Feature
        else if (geoJSON.type && geoJSON.coordinates) {
          mergedFeatures.push({
            type: 'Feature',
            geometry: geoJSON,
            properties: {}
          });
        }
      });
      
      return {
        type: 'FeatureCollection',
        features: mergedFeatures
      };
    },
    
    /**
     * 根据属性过滤 GeoJSON 特征
     * @param {Object} geoJSON - GeoJSON 对象
     * @param {Function} filterFn - 过滤函数,接收 properties 参数,返回布尔值
     * @returns {Object} 过滤后的 GeoJSON 对象
     */
    filterByProperties(geoJSON, filterFn) {
      if (geoJSON.type !== 'FeatureCollection' || !geoJSON.features) {
        return geoJSON;
      }
      
      const filteredFeatures = geoJSON.features.filter(feature => {
        return filterFn(feature.properties || {});
      });
      
      return {
        type: 'FeatureCollection',
        features: filteredFeatures
      };
    },
    
    /**
     * 计算 GeoJSON 中的要素数量
     * @param {Object} geoJSON - GeoJSON 对象
     * @returns {Object} 各类型要素数量
     */
    countFeatures(geoJSON) {
      const counts = {
        total: 0,
        point: 0,
        lineString: 0,
        polygon: 0,
        other: 0
      };
      
      // 没有要素或格式不正确
      if (!geoJSON || typeof geoJSON !== 'object') {
        return counts;
      }
      
      // 处理 FeatureCollection
      if (geoJSON.type === 'FeatureCollection' && Array.isArray(geoJSON.features)) {
        counts.total = geoJSON.features.length;
        
        geoJSON.features.forEach(feature => {
          if (feature.geometry && feature.geometry.type) {
            const type = feature.geometry.type.toLowerCase();
            
            if (type === 'point' || type === 'multipoint') {
              counts.point++;
            } else if (type === 'linestring' || type === 'multilinestring') {
              counts.lineString++;
            } else if (type === 'polygon' || type === 'multipolygon') {
              counts.polygon++;
            } else {
              counts.other++;
            }
          } else {
            counts.other++;
          }
        });
      }
      // 处理单个 Feature
      else if (geoJSON.type === 'Feature' && geoJSON.geometry) {
        counts.total = 1;
        
        const type = geoJSON.geometry.type.toLowerCase();
        if (type === 'point' || type === 'multipoint') {
          counts.point = 1;
        } else if (type === 'linestring' || type === 'multilinestring') {
          counts.lineString = 1;
        } else if (type === 'polygon' || type === 'multipolygon') {
          counts.polygon = 1;
        } else {
          counts.other = 1;
        }
      }
      
      return counts;
    },
    
    /**
     * 简化 GeoJSON 几何图形(减少点的数量)
     * @param {Object} geoJSON - GeoJSON 对象
     * @param {number} tolerance - 简化容差
     * @returns {Object} 简化后的 GeoJSON 对象
     */
    simplifyGeometry(geoJSON, tolerance = 0.00001) {
      // 转换为 OpenLayers 要素
      const features = this.readGeoJSON(geoJSON);
      
      // 对每个要素进行简化
      features.forEach(feature => {
        const geometry = feature.getGeometry();
        if (geometry) {
          const simplifiedGeometry = geometry.simplify(tolerance);
          feature.setGeometry(simplifiedGeometry);
        }
      });
      
      // 转回 GeoJSON
      return this.writeGeoJSON(features);
    }
  };
};

// 使用示例:
const geoJSONProcessor = createGeoJSONProcessor();

// 从 URL 加载 GeoJSON 数据
geoJSONProcessor.loadFromURL('https://example.com/data.geojson')
  .then(features => {
    // 添加到矢量图层
    vectorSource.addFeatures(features);
  })
  .catch(error => {
    console.error('加载 GeoJSON 失败:', error);
  });

// 将要素导出为 GeoJSON
const features = vectorSource.getFeatures();
const geoJSON = geoJSONProcessor.writeGeoJSON(features);
console.log(JSON.stringify(geoJSON, null, 2));

// 过滤 GeoJSON 数据
const filteredGeoJSON = geoJSONProcessor.filterByProperties(geoJSON, 
  properties => properties.population > 100000
);

// 简化几何图形
const simplifiedGeoJSON = geoJSONProcessor.simplifyGeometry(geoJSON, 0.0001);

1.2 空间数据生成器

javascript 复制代码
/**
 * 创建空间数据生成器
 * 功能:生成各种类型的模拟空间数据,用于测试和演示
 * 
 * @returns {Object} - 返回数据生成方法集合
 */
const createSpatialDataGenerator = () => {
  /**
   * 生成随机坐标
   * @param {Array<number>} bounds - 坐标范围 [minX, minY, maxX, maxY]
   * @param {string} projection - 坐标系
   * @returns {Array<number>} 随机坐标 [x, y]
   */
  const generateRandomCoord = (bounds, projection = 'EPSG:4326') => {
    const x = bounds[0] + Math.random() * (bounds[2] - bounds[0]);
    const y = bounds[1] + Math.random() * (bounds[3] - bounds[1]);
    return [x, y];
  };
  
  /**
   * 生成随机颜色
   * @param {boolean} includeAlpha - 是否包含透明度
   * @returns {string} 颜色字符串
   */
  const generateRandomColor = (includeAlpha = false) => {
    const r = Math.floor(Math.random() * 256);
    const g = Math.floor(Math.random() * 256);
    const b = Math.floor(Math.random() * 256);
    
    if (includeAlpha) {
      const a = Math.round((Math.random() * 0.7 + 0.3) * 100) / 100; // 0.3-1.0
      return `rgba(${r}, ${g}, ${b}, ${a})`;
    } else {
      return `rgb(${r}, ${g}, ${b})`;
    }
  };
  
  return {
    /**
     * 生成随机点要素
     * @param {number} count - 要生成的点的数量
     * @param {Object} options - 生成选项
     * @returns {Array<Feature>} 点要素数组
     */
    generatePoints(count, options = {}) {
      const points = [];
      const bounds = options.bounds || [-180, -85, 180, 85]; // 默认全球范围
      const projection = options.projection || 'EPSG:4326';
      
      for (let i = 0; i < count; i++) {
        const coord = generateRandomCoord(bounds, projection);
        
        // 创建点要素
        const point = new Feature({
          geometry: new Point(coord),
          // 添加随机属性
          properties: {
            id: `point-${i}`,
            name: options.namePrefix ? `${options.namePrefix}-${i}` : `点 ${i}`,
            value: Math.floor(Math.random() * 100),
            color: generateRandomColor(),
            timestamp: Date.now() - Math.floor(Math.random() * 30 * 24 * 60 * 60 * 1000) // 随机时间(30天内)
          }
        });
        
        // 设置ID
        point.setId(`point-${i}`);
        
        // 添加自定义属性
        if (options.customProperties) {
          for (const key in options.customProperties) {
            if (typeof options.customProperties[key] === 'function') {
              point.set(key, options.customProperties[key](i, coord));
            } else {
              point.set(key, options.customProperties[key]);
            }
          }
        }
        
        points.push(point);
      }
      
      return points;
    },
    
    /**
     * 生成随机线要素
     * @param {number} count - 要生成的线的数量
     * @param {Object} options - 生成选项
     * @returns {Array<Feature>} 线要素数组
     */
    generateLines(count, options = {}) {
      const lines = [];
      const bounds = options.bounds || [-180, -85, 180, 85]; // 默认全球范围
      const projection = options.projection || 'EPSG:4326';
      const minPoints = options.minPoints || 2;
      const maxPoints = options.maxPoints || 10;
      
      for (let i = 0; i < count; i++) {
        // 确定线的点数
        const numPoints = Math.floor(Math.random() * (maxPoints - minPoints + 1)) + minPoints;
        const coords = [];
        
        // 生成路径点
        let lastCoord = generateRandomCoord(bounds, projection);
        coords.push(lastCoord);
        
        for (let j = 1; j < numPoints; j++) {
          // 在上一个点附近生成新点,创建连续的线
          const maxOffset = options.maxSegmentLength || 1; // 最大线段长度
          const dx = (Math.random() * 2 - 1) * maxOffset;
          const dy = (Math.random() * 2 - 1) * maxOffset;
          
          const newCoord = [
            Math.max(bounds[0], Math.min(bounds[2], lastCoord[0] + dx)),
            Math.max(bounds[1], Math.min(bounds[3], lastCoord[1] + dy))
          ];
          
          coords.push(newCoord);
          lastCoord = newCoord;
        }
        
        // 创建线要素
        const line = new Feature({
          geometry: new LineString(coords),
          // 添加随机属性
          properties: {
            id: `line-${i}`,
            name: options.namePrefix ? `${options.namePrefix}-${i}` : `线 ${i}`,
            length: Math.random() * 100,
            color: generateRandomColor(),
            width: Math.floor(Math.random() * 5) + 1
          }
        });
        
        // 设置ID
        line.setId(`line-${i}`);
        
        // 添加自定义属性
        if (options.customProperties) {
          for (const key in options.customProperties) {
            if (typeof options.customProperties[key] === 'function') {
              line.set(key, options.customProperties[key](i, coords));
            } else {
              line.set(key, options.customProperties[key]);
            }
          }
        }
        
        lines.push(line);
      }
      
      return lines;
    },
    
    /**
     * 生成随机多边形要素
     * @param {number} count - 要生成的多边形的数量
     * @param {Object} options - 生成选项
     * @returns {Array<Feature>} 多边形要素数组
     */
    generatePolygons(count, options = {}) {
      const polygons = [];
      const bounds = options.bounds || [-180, -85, 180, 85]; // 默认全球范围
      const projection = options.projection || 'EPSG:4326';
      const minVertices = options.minVertices || 3;
      const maxVertices = options.maxVertices || 8;
      
      for (let i = 0; i < count; i++) {
        // 确定多边形的顶点数
        const numVertices = Math.floor(Math.random() * (maxVertices - minVertices + 1)) + minVertices;
        
        // 生成中心点
        const center = generateRandomCoord(bounds, projection);
        const radius = options.maxRadius || 1; // 最大半径
        
        // 生成多边形顶点
        const vertices = [];
        for (let j = 0; j < numVertices; j++) {
          const angle = (j / numVertices) * 2 * Math.PI;
          // 添加一些随机性,使多边形不那么规则
          const currentRadius = radius * (0.7 + Math.random() * 0.6); // 半径变化 0.7-1.3
          
          const x = center[0] + currentRadius * Math.cos(angle);
          const y = center[1] + currentRadius * Math.sin(angle);
          
          // 确保坐标在范围内
          const boundedX = Math.max(bounds[0], Math.min(bounds[2], x));
          const boundedY = Math.max(bounds[1], Math.min(bounds[3], y));
          
          vertices.push([boundedX, boundedY]);
        }
        
        // 闭合多边形
        vertices.push(vertices[0]);
        
        // 创建多边形要素
        const polygon = new Feature({
          geometry: new Polygon([vertices]),
          // 添加随机属性
          properties: {
            id: `polygon-${i}`,
            name: options.namePrefix ? `${options.namePrefix}-${i}` : `多边形 ${i}`,
            area: Math.random() * 100,
            fillColor: generateRandomColor(true),
            strokeColor: generateRandomColor()
          }
        });
        
        // 设置ID
        polygon.setId(`polygon-${i}`);
        
        // 添加自定义属性
        if (options.customProperties) {
          for (const key in options.customProperties) {
            if (typeof options.customProperties[key] === 'function') {
              polygon.set(key, options.customProperties[key](i, vertices));
            } else {
              polygon.set(key, options.customProperties[key]);
            }
          }
        }
        
        polygons.push(polygon);
      }
      
      return polygons;
    },
    
    /**
     * 生成网格数据
     * @param {Object} options - 网格选项
     * @returns {Array<Feature>} 要素数组
     */
    generateGrid(options = {}) {
      const bounds = options.bounds || [-180, -85, 180, 85];
      const cellSize = options.cellSize || 1; // 网格单元大小
      const projection = options.projection || 'EPSG:4326';
      const valueFunction = options.valueFunction || (() => Math.random() * 100);
      const features = [];
      
      const xSteps = Math.ceil((bounds[2] - bounds[0]) / cellSize);
      const ySteps = Math.ceil((bounds[3] - bounds[1]) / cellSize);
      
      for (let x = 0; x < xSteps; x++) {
        for (let y = 0; y < ySteps; y++) {
          const minX = bounds[0] + x * cellSize;
          const minY = bounds[1] + y * cellSize;
          const maxX = Math.min(bounds[2], minX + cellSize);
          const maxY = Math.min(bounds[3], minY + cellSize);
          
          // 生成单元格值
          const value = valueFunction(x, y, minX, minY);
          
          // 创建网格单元
          const cell = new Feature({
            geometry: new Polygon([[
              [minX, minY],
              [maxX, minY],
              [maxX, maxY],
              [minX, maxY],
              [minX, minY]
            ]]),
            properties: {
              id: `cell-${x}-${y}`,
              x: x,
              y: y,
              value: value,
              color: options.colorFunction ? options.colorFunction(value) : generateRandomColor(true)
            }
          });
          
          // 设置ID
          cell.setId(`cell-${x}-${y}`);
          
          // 添加自定义属性
          if (options.customProperties) {
            for (const key in options.customProperties) {
              if (typeof options.customProperties[key] === 'function') {
                cell.set(key, options.customProperties[key](x, y, value));
              } else {
                cell.set(key, options.customProperties[key]);
              }
            }
          }
          
          features.push(cell);
        }
      }
      
      return features;
    },
    
    /**
     * 生成热力图数据
     * @param {number} count - 点的数量
     * @param {Object} options - 生成选项
     * @returns {Array<Feature>} 热力图点要素
     */
    generateHeatmapData(count, options = {}) {
      const points = [];
      const bounds = options.bounds || [-180, -85, 180, 85];
      const projection = options.projection || 'EPSG:4326';
      
      // 创建几个热点中心
      const hotspots = options.hotspots || [];
      if (hotspots.length === 0) {
        // 如果没有提供热点,则随机生成2-5个
        const numHotspots = Math.floor(Math.random() * 4) + 2;
        for (let i = 0; i < numHotspots; i++) {
          hotspots.push({
            center: generateRandomCoord(bounds, projection),
            intensity: Math.random() * 0.8 + 0.2, // 0.2-1.0
            radius: Math.random() * (options.maxRadius || 10) + (options.minRadius || 2)
          });
        }
      }
      
      // 根据热点生成点
      for (let i = 0; i < count; i++) {
        // 随机选择一个热点
        const hotspotIndex = Math.floor(Math.random() * hotspots.length);
        const hotspot = hotspots[hotspotIndex];
        
        // 在热点周围生成点,距离越远概率越小
        const angle = Math.random() * 2 * Math.PI;
        // 使用正态分布使点在中心附近密集
        const distance = Math.abs(Math.random() + Math.random() + Math.random() - 1.5) * hotspot.radius;
        
        const x = hotspot.center[0] + distance * Math.cos(angle);
        const y = hotspot.center[1] + distance * Math.sin(angle);
        
        // 确保坐标在范围内
        const boundedX = Math.max(bounds[0], Math.min(bounds[2], x));
        const boundedY = Math.max(bounds[1], Math.min(bounds[3], y));
        
        // 计算权重 - 距离中心越近权重越大
        const weight = Math.max(0.1, 1 - (distance / hotspot.radius)) * hotspot.intensity;
        
        // 创建点要素
        const point = new Feature({
          geometry: new Point([boundedX, boundedY]),
          weight: weight, // 热力图权重
          properties: {
            id: `heatpoint-${i}`,
            weight: weight,
            hotspotId: hotspotIndex
          }
        });
        
        // 设置ID
        point.setId(`heatpoint-${i}`);
        
        // 添加自定义属性
        if (options.customProperties) {
          for (const key in options.customProperties) {
            if (typeof options.customProperties[key] === 'function') {
              point.set(key, options.customProperties[key](i, [boundedX, boundedY], weight));
            } else {
              point.set(key, options.customProperties[key]);
            }
          }
        }
        
        points.push(point);
      }
      
      return points;
    },
    
    /**
     * 生成聚类点数据
     * @param {number} clusters - 聚类数量
     * @param {number} pointsPerCluster - 每个聚类的点数
     * @param {Object} options - 生成选项
     * @returns {Array<Feature>} 点要素数组
     */
    generateClusteredPoints(clusters, pointsPerCluster, options = {}) {
      const points = [];
      const bounds = options.bounds || [-180, -85, 180, 85];
      const projection = options.projection || 'EPSG:4326';
      
      // 生成聚类中心
      const clusterCenters = [];
      for (let i = 0; i < clusters; i++) {
        clusterCenters.push({
          center: generateRandomCoord(bounds, projection),
          radius: options.clusterRadius || 2,
          category: options.categoryFunction ? options.categoryFunction(i) : `类别${i + 1}`
        });
      }
      
      // 为每个聚类生成点
      let pointId = 0;
      for (let i = 0; i < clusters; i++) {
        const cluster = clusterCenters[i];
        
        for (let j = 0; j < pointsPerCluster; j++) {
          // 在聚类中心周围生成点
          const angle = Math.random() * 2 * Math.PI;
          // 距离使用平方根分布,使点分布更自然
          const distance = Math.sqrt(Math.random()) * cluster.radius;
          
          const x = cluster.center[0] + distance * Math.cos(angle);
          const y = cluster.center[1] + distance * Math.sin(angle);
          
          // 确保坐标在范围内
          const boundedX = Math.max(bounds[0], Math.min(bounds[2], x));
          const boundedY = Math.max(bounds[1], Math.min(bounds[3], y));
          
          // 创建点要素
          const point = new Feature({
            geometry: new Point([boundedX, boundedY]),
            // 添加聚类信息
            properties: {
              id: `cluster-point-${pointId}`,
              name: `点 ${pointId}`,
              clusterId: i,
              category: cluster.category,
                            color: options.colorByCluster ? 
                    (typeof options.colorByCluster === 'function' ? 
                      options.colorByCluster(i) : 
                      generateRandomColor()) : 
                    generateRandomColor()
            }
          });
          
          // 设置ID
          point.setId(`cluster-point-${pointId}`);
          
          // 添加自定义属性
          if (options.customProperties) {
            for (const key in options.customProperties) {
              if (typeof options.customProperties[key] === 'function') {
                point.set(key, options.customProperties[key](pointId, i, [boundedX, boundedY]));
              } else {
                point.set(key, options.customProperties[key]);
              }
            }
          }
          
          points.push(point);
          pointId++;
        }
      }
      
      return points;
    },
    
    /**
     * 生成 GeoJSON 数据
     * @param {Object} options - 生成选项
     * @returns {Object} GeoJSON 对象
     */
    generateGeoJSON(options = {}) {
      const features = [];
      
      // 生成点要素
      if (options.numPoints) {
        const points = this.generatePoints(options.numPoints, options.pointOptions || {});
        features.push(...points);
      }
      
      // 生成线要素
      if (options.numLines) {
        const lines = this.generateLines(options.numLines, options.lineOptions || {});
        features.push(...lines);
      }
      
      // 生成多边形要素
      if (options.numPolygons) {
        const polygons = this.generatePolygons(options.numPolygons, options.polygonOptions || {});
        features.push(...polygons);
      }
      
      // 创建 GeoJSON 格式解析器
      const geoJSONFormat = new GeoJSON();
      
      // 将要素转换为 GeoJSON
      return geoJSONFormat.writeFeaturesObject(features, {
        dataProjection: options.dataProjection || 'EPSG:4326',
        featureProjection: options.featureProjection || 'EPSG:3857'
      });
    }
  };
};

// 使用示例:
const spatialDataGenerator = createSpatialDataGenerator();

// 生成随机点要素
const randomPoints = spatialDataGenerator.generatePoints(50, {
  bounds: [100, 20, 120, 40], // 中国部分区域
  namePrefix: '观测点'
});

// 添加到矢量图层
vectorSource.addFeatures(randomPoints);

// 生成聚类点
const clusteredPoints = spatialDataGenerator.generateClusteredPoints(5, 20, {
  bounds: [100, 20, 120, 40],
  colorByCluster: (clusterId) => {
    const colors = ['#FF5733', '#33FF57', '#3357FF', '#F3FF33', '#FF33F3'];
    return colors[clusterId % colors.length];
  }
});

// 生成并导出 GeoJSON
const demoGeoJSON = spatialDataGenerator.generateGeoJSON({
  numPoints: 20,
  numLines: 10,
  numPolygons: 5,
  pointOptions: { bounds: [100, 20, 120, 40] },
  lineOptions: { bounds: [100, 20, 120, 40] },
  polygonOptions: { bounds: [100, 20, 120, 40] }
});

console.log(JSON.stringify(demoGeoJSON, null, 2)); 

2. 地图交互性能优化工具

2.1 要素聚类工具

javascript 复制代码
/**
 * 创建要素聚类工具
 * 功能:对大量点要素进行聚类显示,提高地图渲染性能
 * 
 * @param {VectorSource} source - 矢量数据源
 * @param {Object} options - 聚类选项
 * @returns {Object} - 返回聚类控制方法集合
 */
const createClusteringTool = (source, options = {}) => {
  // 创建聚类数据源
  const clusterSource = new Cluster({
    source: source,
    distance: options.distance || 40,
    minDistance: options.minDistance || 20,
    geometryFunction: options.geometryFunction || undefined
  });
  
  // 创建聚类图层
  const clusterLayer = new VectorLayer({
    source: clusterSource,
    style: (feature) => {
      const size = feature.get('features').length;
      
      // 单个要素使用原始样式
      if (size === 1) {
        return options.singlePointStyle || new Style({
          image: new CircleStyle({
            radius: 5,
            fill: new Fill({
              color: '#3399CC'
            }),
            stroke: new Stroke({
              color: '#fff',
              width: 1
            })
          })
        });
      }
      
      // 聚类样式
      return new Style({
        image: new CircleStyle({
          radius: Math.min(20, 10 + Math.log2(size) * 2), // 根据点数量调整大小
          fill: new Fill({
            color: options.clusterFillColor || '#3399CC'
          }),
          stroke: new Stroke({
            color: options.clusterStrokeColor || '#fff',
            width: options.clusterStrokeWidth || 2
          })
        }),
        text: new Text({
          text: size.toString(),
          fill: new Fill({
            color: options.clusterTextColor || '#fff'
          }),
          font: options.clusterFont || '12px Arial'
        })
      });
    }
  });
  
  // 添加点击事件处理
  let selectInteraction;
  let clusterEventKey;
  
  /**
   * 初始化聚类点击事件
   * @param {Map} map - OpenLayers 地图实例
   */
  const initClusterClick = (map) => {
    if (clusterEventKey) {
      unByKey(clusterEventKey);
    }
    
    clusterEventKey = map.on('click', (evt) => {
      const feature = map.forEachFeatureAtPixel(evt.pixel, (feature) => feature);
      
      if (feature && feature.get('features')) {
        const features = feature.get('features');
        
        // 如果只有一个点,不处理
        if (features.length === 1) {
          return;
        }
        
        // 如果点击的是聚类,根据当前缩放级别决定行为
        const zoom = map.getView().getZoom();
        const maxZoom = map.getView().getMaxZoom();
        
        if (zoom < maxZoom) {
          // 放大到合适的级别以显示聚类中的点
          const extent = createEmpty();
          features.forEach((f) => {
            const geometry = f.getGeometry();
            if (geometry) {
              extendExtent(extent, geometry.getExtent());
            }
          });
          
          // 放大到聚类范围
          map.getView().fit(extent, {
            duration: 500,
            padding: [50, 50, 50, 50],
            maxZoom: zoom + 2 // 限制最大放大级别
          });
        } else {
          // 已经是最大缩放级别,显示聚类中的所有点信息
          if (options.onClusterClick) {
            options.onClusterClick(features, evt);
          }
        }
      }
    });
  };
  
  return {
    /**
     * 获取聚类图层
     * @returns {VectorLayer} 聚类图层
     */
    getLayer() {
      return clusterLayer;
    },
    
    /**
     * 获取聚类数据源
     * @returns {Cluster} 聚类数据源
     */
    getSource() {
      return clusterSource;
    },
    
    /**
     * 添加到地图
     * @param {Map} map - OpenLayers 地图实例
     */
    addToMap(map) {
      map.addLayer(clusterLayer);
      initClusterClick(map);
    },
    
    /**
     * 从地图移除
     * @param {Map} map - OpenLayers 地图实例
     */
    removeFromMap(map) {
      if (clusterEventKey) {
        unByKey(clusterEventKey);
        clusterEventKey = null;
      }
      
      map.removeLayer(clusterLayer);
    },
    
    /**
     * 设置聚类距离
     * @param {number} distance - 聚类距离(像素)
     */
    setDistance(distance) {
      clusterSource.setDistance(distance);
    },
    
    /**
     * 设置最小距离
     * @param {number} minDistance - 最小聚类距离(像素)
     */
    setMinDistance(minDistance) {
      clusterSource.setMinDistance(minDistance);
    },
    
    /**
     * 刷新聚类
     */
    refresh() {
      source.refresh();
      clusterSource.refresh();
    },
    
    /**
     * 获取指定位置的聚类信息
     * @param {Array<number>} coordinate - 坐标
     * @param {Map} map - OpenLayers 地图实例
     * @returns {Object|null} 聚类信息或 null
     */
    getClusterInfo(coordinate, map) {
      const pixel = map.getPixelFromCoordinate(coordinate);
      const feature = map.forEachFeatureAtPixel(pixel, (feature) => feature);
      
      if (feature && feature.get('features')) {
        const features = feature.get('features');
        return {
          size: features.length,
          features: features,
          coordinate: coordinate
        };
      }
      
      return null;
    }
  };
};

// 使用示例:
const pointSource = new VectorSource();
// 添加大量点数据
pointSource.addFeatures(spatialDataGenerator.generatePoints(1000));

// 创建聚类工具
const clusteringTool = createClusteringTool(pointSource, {
  distance: 50,
  clusterFillColor: '#FF5733',
  onClusterClick: (features, event) => {
    console.log(`聚类包含 ${features.length} 个点`);
    // 在这里可以显示聚类信息弹窗等
  }
});

// 添加到地图
clusteringTool.addToMap(map);

// 调整聚类距离
clusteringTool.setDistance(80);

2.2 地图性能监控器

javascript 复制代码
/**
 * 创建地图性能监控器
 * 功能:监控地图渲染性能,提供性能优化建议
 * 
 * @param {Map} map - OpenLayers 地图实例
 * @returns {Object} - 返回性能监控方法集合
 */
const createPerformanceMonitor = (map) => {
  // 性能数据
  const performanceData = {
    frameRate: 0,
    renderTime: 0,
    featureCount: 0,
    layerCount: 0,
    memoryUsage: 0,
    eventCount: 0
  };
  
  // 监控状态
  let isMonitoring = false;
  let frameCount = 0;
  let lastFrameTime = 0;
  let monitoringInterval = null;
  
  // 保存添加的事件监听器
  const eventKeys = [];
  
  /**
   * 计算特征数量
   * @returns {number} 特征总数
   */
  const countFeatures = () => {
    let count = 0;
    map.getLayers().forEach(layer => {
      if (layer instanceof VectorLayer) {
        const source = layer.getSource();
        if (source.getFeatures) {
          count += source.getFeatures().length;
        } else if (source instanceof Cluster) {
          count += source.getSource().getFeatures().length;
        }
      }
    });
    return count;
  };
  
  /**
   * 计算内存使用情况(近似值)
   * @returns {number} 近似内存使用(MB)
   */
  const estimateMemoryUsage = () => {
    if (window.performance && window.performance.memory) {
      return Math.round(window.performance.memory.usedJSHeapSize / (1024 * 1024));
    }
    return 0;
  };
  
  /**
   * 每帧计算帧率
   */
  const calculateFrameRate = () => {
    const now = performance.now();
    frameCount++;
    
    // 每秒更新一次帧率
    if (now - lastFrameTime >= 1000) {
      performanceData.frameRate = Math.round(frameCount * 1000 / (now - lastFrameTime));
      frameCount = 0;
      lastFrameTime = now;
    }
  };
  
  /**
   * 测量渲染时间
   * @param {Object} event - 渲染事件对象
   */
  const measureRenderTime = (event) => {
    if (event.frameState) {
      performanceData.renderTime = event.frameState.time;
    }
  };
  
  return {
    /**
     * 启动性能监控
     * @param {number} interval - 更新间隔(毫秒)
     */
    start(interval = 1000) {
      if (isMonitoring) return;
      
      isMonitoring = true;
      lastFrameTime = performance.now();
      
      // 监听渲染事件
      eventKeys.push(
        map.on('postrender', calculateFrameRate),
        map.on('postrender', measureRenderTime)
      );
      
      // 定期收集其他性能数据
      monitoringInterval = setInterval(() => {
        performanceData.featureCount = countFeatures();
        performanceData.layerCount = map.getLayers().getLength();
        performanceData.memoryUsage = estimateMemoryUsage();
        performanceData.eventCount = eventKeys.length;
      }, interval);
    },
    
    /**
     * 停止性能监控
     */
    stop() {
      if (!isMonitoring) return;
      
      isMonitoring = false;
      
      // 移除事件监听器
      eventKeys.forEach(key => unByKey(key));
      eventKeys.length = 0;
      
      // 清除定时器
      if (monitoringInterval) {
        clearInterval(monitoringInterval);
        monitoringInterval = null;
      }
    },
    
    /**
     * 获取性能数据
     * @returns {Object} 性能数据
     */
    getPerformanceData() {
      return { ...performanceData };
    },
    
    /**
     * 获取性能分析报告
     * @returns {Object} 性能分析和建议
     */
    getPerformanceReport() {
      const report = {
        timestamp: new Date().toISOString(),
        metrics: { ...performanceData },
        status: 'good',
        issues: [],
        suggestions: []
      };
      
      // 检查帧率
      if (performanceData.frameRate < 30) {
        report.status = 'warning';
        report.issues.push('帧率较低,可能导致地图交互不流畅');
        
        if (performanceData.featureCount > 1000) {
          report.suggestions.push('考虑使用聚类或矢量切片减少要素数量');
        }
        
        if (performanceData.layerCount > 5) {
          report.suggestions.push('减少同时显示的图层数量');
        }
      }
      
      // 检查特征数量
      if (performanceData.featureCount > 5000) {
        report.status = 'warning';
        report.issues.push('要素数量过多,可能影响性能');
        report.suggestions.push('使用矢量切片(VectorTile)替代普通矢量图层');
        report.suggestions.push('启用要素简化(simplify)降低复杂度');
      }
      
      // 渲染时间检查
      if (performanceData.renderTime > 50) {
        report.status = 'warning';
        report.issues.push('渲染时间较长');
        report.suggestions.push('使用更简单的样式,减少线宽和填充透明度的使用');
      }
      
      // 内存使用检查
      if (performanceData.memoryUsage > 500) {
        report.status = 'warning';
        report.issues.push('内存使用较高');
        report.suggestions.push('检查是否有内存泄漏,确保正确移除不再使用的图层和交互');
      }
      
      return report;
    },
    
    /**
     * 应用推荐的性能优化
     * @param {Map} map - OpenLayers 地图实例
     */
    applyOptimizations() {
      const report = this.getPerformanceReport();
      let optimizationsApplied = [];
      
      // 如果性能良好,不需要优化
      if (report.status === 'good') {
        return ['当前性能良好,无需优化'];
      }
      
      // 应用优化
      if (performanceData.featureCount > 1000) {
        // 对每个矢量图层应用渲染策略
        map.getLayers().forEach(layer => {
          if (layer instanceof VectorLayer && !(layer.getSource() instanceof Cluster)) {
            // 设置渲染缓冲区
            layer.setRenderBuffer(100);
            
            // 设置渲染模式为 'image',提高大量要素的渲染性能
            layer.setRenderMode('image');
            
            optimizationsApplied.push('设置矢量图层为图片渲染模式');
          }
        });
      }
      
      // 调整渲染帧率
      if (performanceData.frameRate < 20 && performanceData.featureCount > 2000) {
        // 降低动画效果的平滑度,提高性能
        map.getView().setConstrainResolution(true);
        optimizationsApplied.push('禁用分辨率插值以提高性能');
      }
      
      return optimizationsApplied;
    }
  };
};

// 使用示例:
const performanceMonitor = createPerformanceMonitor(map);

// 启动性能监控
performanceMonitor.start();

// 获取性能数据
setInterval(() => {
  const data = performanceMonitor.getPerformanceData();
  console.log(`帧率: ${data.frameRate} FPS, 要素数: ${data.featureCount}`);
}, 5000);

// 获取性能报告
const report = performanceMonitor.getPerformanceReport();
if (report.status !== 'good') {
  console.warn('性能问题:', report.issues);
  console.info('优化建议:', report.suggestions);
  
  // 应用自动优化
  const appliedOptimizations = performanceMonitor.applyOptimizations();
  console.info('已应用优化:', appliedOptimizations);
}

// 停止监控
// performanceMonitor.stop();
相关推荐
独泪了无痕1 小时前
深入浅析Vue3中的生命周期钩子函数
前端·vue.js
来来走走1 小时前
Flutter开发 了解Scaffold
android·开发语言·flutter
小白白一枚1111 小时前
vue和react的框架原理
前端·vue.js·react.js
字节逆旅1 小时前
从一次爬坑看前端的出路
前端·后端·程序员
xiangweiqiang2 小时前
用phpstudy安装php8.2后报错:意思是找不到php_redis.dll拓展时
开发语言·php
若梦plus2 小时前
微前端之样式隔离、JS隔离、公共依赖、路由状态更新、通信方式对比
前端
mitt_2 小时前
go语言变量
开发语言·后端·golang
若梦plus2 小时前
Babel中微内核&插件化思想的应用
前端·babel
若梦plus2 小时前
微前端中微内核&插件化思想的应用
前端
若梦plus2 小时前
服务化架构中微内核&插件化思想的应用
前端