
1. GeoJSON 数据处理工具
1.1 GeoJSON 转换与处理工具
/*** 创建 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;}};// 处理不同类型的 GeoJSONif (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 => {// 处理 FeatureCollectionif (geoJSON.type === 'FeatureCollection' && geoJSON.features) {mergedFeatures.push(...geoJSON.features);}// 处理单个 Featureelse if (geoJSON.type === 'Feature') {mergedFeatures.push(geoJSON);}// 处理几何对象,将其转换为 Featureelse 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;}// 处理 FeatureCollectionif (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++;}});}// 处理单个 Featureelse 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);}});// 转回 GeoJSONreturn 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 空间数据生成器
/*** 创建空间数据生成器* 功能:生成各种类型的模拟空间数据,用于测试和演示* * @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.0return `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天内)}});// 设置IDpoint.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}});// 设置IDline.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.3const 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()}});// 设置IDpolygon.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)}});// 设置IDcell.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.0radius: 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}});// 设置IDpoint.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()}});// 设置IDpoint.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();// 将要素转换为 GeoJSONreturn 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 要素聚类工具
/*** 创建要素聚类工具* 功能:对大量点要素进行聚类显示,提高地图渲染性能* * @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 地图性能监控器
/*** 创建地图性能监控器* 功能:监控地图渲染性能,提供性能优化建议* * @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();