第08章 - 矢量数据与样式

第08章 - 矢量数据与样式

8.1 Feature 要素详解

8.1.1 要素的概念

Feature(要素)是 OpenLayers 中表示地理实体的基本单元,由几何对象(Geometry)和属性(Properties)组成。

┌─────────────────────────────────────────────────────────────┐
│                      Feature 结构                           │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌─────────────────────────────────────────────────────┐  │
│   │                    Feature                           │  │
│   │                                                      │  │
│   │   ┌─────────────────────┐ ┌─────────────────────┐   │  │
│   │   │     Geometry        │ │    Properties       │   │  │
│   │   │     (几何对象)       │ │    (属性)           │   │  │
│   │   │                     │ │                     │   │  │
│   │   │   • Point           │ │   • id: "1"         │   │  │
│   │   │   • LineString      │ │   • name: "北京"    │   │  │
│   │   │   • Polygon         │ │   • population: ... │   │  │
│   │   │   • Multi*          │ │   • custom: ...     │   │  │
│   │   │                     │ │                     │   │  │
│   │   └─────────────────────┘ └─────────────────────┘   │  │
│   │                                                      │  │
│   │   ┌─────────────────────┐                            │  │
│   │   │     Style           │                            │  │
│   │   │     (样式)          │                            │  │
│   │   └─────────────────────┘                            │  │
│   │                                                      │  │
│   └─────────────────────────────────────────────────────┘  │
│                                                             │
└─────────────────────────────────────────────────────────────┘

8.1.2 创建要素

import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import LineString from 'ol/geom/LineString';
import Polygon from 'ol/geom/Polygon';
import { fromLonLat } from 'ol/proj';

// 创建点要素
const pointFeature = new Feature({
  geometry: new Point(fromLonLat([116.4074, 39.9042])),
  name: '北京',
  type: 'city',
  population: 21540000
});

// 设置要素 ID
pointFeature.setId('beijing_1');

// 创建线要素
const lineFeature = new Feature({
  geometry: new LineString([
    fromLonLat([116.4074, 39.9042]),
    fromLonLat([121.4737, 31.2304]),
    fromLonLat([113.2644, 23.1291])
  ]),
  name: '北京-上海-广州',
  type: 'route'
});

// 创建面要素
const polygonFeature = new Feature({
  geometry: new Polygon([[
    fromLonLat([116.0, 39.0]),
    fromLonLat([117.0, 39.0]),
    fromLonLat([117.0, 40.0]),
    fromLonLat([116.0, 40.0]),
    fromLonLat([116.0, 39.0]) // 闭合
  ]]),
  name: '区域A',
  type: 'area'
});

// 使用构建器模式
function createFeature(geomType, coordinates, properties) {
  const GeomClass = {
    Point: Point,
    LineString: LineString,
    Polygon: Polygon
  }[geomType];
  
  const feature = new Feature({
    geometry: new GeomClass(coordinates),
    ...properties
  });
  
  if (properties.id) {
    feature.setId(properties.id);
  }
  
  return feature;
}

8.1.3 要素操作

// ID 操作
feature.setId('unique_id');
const id = feature.getId();

// 几何操作
const geometry = feature.getGeometry();
feature.setGeometry(new Point(fromLonLat([121.4737, 31.2304])));

// 属性操作
feature.set('name', '上海');
const name = feature.get('name');

// 获取所有属性
const properties = feature.getProperties();
// 结果: { geometry: Point, name: '上海', ... }

// 批量设置属性
feature.setProperties({
  name: '新名称',
  type: '新类型',
  updated: Date.now()
});

// 删除属性
feature.unset('updated');

// 克隆要素
const clonedFeature = feature.clone();

// 样式操作
feature.setStyle(new Style({ ... }));
const style = feature.getStyle();
const styleFunction = feature.getStyleFunction();

// 事件监听
feature.on('change', () => {
  console.log('要素变化');
});

feature.on('change:geometry', () => {
  console.log('几何变化');
});

feature.on('propertychange', (event) => {
  console.log('属性变化:', event.key);
});

8.2 Geometry 几何对象

8.2.1 几何类型

import Point from 'ol/geom/Point';
import MultiPoint from 'ol/geom/MultiPoint';
import LineString from 'ol/geom/LineString';
import MultiLineString from 'ol/geom/MultiLineString';
import LinearRing from 'ol/geom/LinearRing';
import Polygon from 'ol/geom/Polygon';
import MultiPolygon from 'ol/geom/MultiPolygon';
import Circle from 'ol/geom/Circle';
import GeometryCollection from 'ol/geom/GeometryCollection';

// Point - 点
const point = new Point([116.4074, 39.9042]);

// MultiPoint - 多点
const multiPoint = new MultiPoint([
  [116.4074, 39.9042],
  [121.4737, 31.2304]
]);

// LineString - 线
const line = new LineString([
  [116.4074, 39.9042],
  [121.4737, 31.2304]
]);

// MultiLineString - 多线
const multiLine = new MultiLineString([
  [[116, 39], [117, 40]],
  [[118, 38], [119, 39]]
]);

// Polygon - 多边形
const polygon = new Polygon([
  // 外环
  [[116, 39], [117, 39], [117, 40], [116, 40], [116, 39]],
  // 内环(孔洞)
  [[116.2, 39.2], [116.8, 39.2], [116.8, 39.8], [116.2, 39.8], [116.2, 39.2]]
]);

// MultiPolygon - 多面
const multiPolygon = new MultiPolygon([
  [[[116, 39], [117, 39], [117, 40], [116, 40], [116, 39]]],
  [[[118, 38], [119, 38], [119, 39], [118, 39], [118, 38]]]
]);

// Circle - 圆(仅用于编辑,不能序列化)
const circle = new Circle([116.4074, 39.9042], 1000); // 半径1000

// GeometryCollection - 几何集合
const collection = new GeometryCollection([point, line, polygon]);

8.2.2 几何操作

// 获取类型
const type = geometry.getType(); // 'Point', 'LineString', 'Polygon', etc.

// 获取坐标
const coords = point.getCoordinates();           // [x, y]
const lineCoords = line.getCoordinates();        // [[x1, y1], [x2, y2], ...]
const polygonCoords = polygon.getCoordinates();  // [[[...]], [[...]]]

// 设置坐标
point.setCoordinates([121.4737, 31.2304]);
line.setCoordinates([[116, 39], [117, 40], [118, 41]]);

// 获取范围
const extent = geometry.getExtent(); // [minX, minY, maxX, maxY]

// 获取长度/面积
import { getLength, getArea } from 'ol/sphere';

const length = getLength(line, { projection: 'EPSG:3857' }); // 米
const area = getArea(polygon, { projection: 'EPSG:3857' });  // 平方米

// 简化几何
const simplified = geometry.simplify(tolerance);

// 变换几何
geometry.transform('EPSG:4326', 'EPSG:3857');

// 克隆几何
const cloned = geometry.clone();

// 相交测试
const isIntersecting = geometry.intersectsCoordinate(coordinate);
const isInExtent = geometry.intersectsExtent(extent);

// 获取最近点
const closestPoint = geometry.getClosestPoint(coordinate);

// 平移几何
geometry.translate(deltaX, deltaY);

// 缩放几何
geometry.scale(sx, sy, anchor);

// 旋转几何
geometry.rotate(angle, anchor);

8.2.3 几何工具函数

import { buffer, union, intersection, difference } from 'ol/geom/flat/';
import { circular } from 'ol/geom/Polygon';
import { getCenter, getWidth, getHeight, boundingExtent } from 'ol/extent';

// 创建圆形多边形
const circularPolygon = circular(
  [116.4074, 39.9042], // 中心点(经纬度)
  1000,                // 半径(米)
  64                   // 边数
);

// 计算几何中心
const center = getCenter(geometry.getExtent());

// 计算边界框
const bbox = boundingExtent([
  [116, 39],
  [117, 40],
  [118, 39]
]);

// 几何验证
function isValidGeometry(geometry) {
  const type = geometry.getType();
  
  switch (type) {
    case 'Polygon':
    case 'MultiPolygon':
      return geometry.getArea() > 0;
    case 'LineString':
    case 'MultiLineString':
      return geometry.getLength() > 0;
    case 'Point':
    case 'MultiPoint':
      return true;
    default:
      return true;
  }
}

8.3 Style 样式系统

8.3.1 样式组成

import { Style, Fill, Stroke, Circle, Icon, Text, RegularShape } from 'ol/style';

// 完整样式示例
const fullStyle = new Style({
  // 填充样式(用于面)
  fill: new Fill({
    color: 'rgba(66, 133, 244, 0.3)'
  }),
  
  // 描边样式(用于线和面的边界)
  stroke: new Stroke({
    color: '#4285F4',
    width: 2,
    lineDash: [4, 4],      // 虚线
    lineDashOffset: 0,
    lineCap: 'round',      // 线端样式: butt, round, square
    lineJoin: 'round',     // 连接样式: bevel, round, miter
    miterLimit: 10
  }),
  
  // 点样式 - 圆形
  image: new Circle({
    radius: 8,
    fill: new Fill({ color: '#4285F4' }),
    stroke: new Stroke({ color: '#fff', width: 2 }),
    displacement: [0, 0],
    scale: 1,
    rotation: 0
  }),
  
  // 文本样式
  text: new Text({
    text: '标签文本',
    font: 'bold 14px sans-serif',
    fill: new Fill({ color: '#333' }),
    stroke: new Stroke({ color: '#fff', width: 3 }),
    offsetX: 0,
    offsetY: -20,
    textAlign: 'center',       // left, center, right
    textBaseline: 'middle',    // top, middle, bottom
    rotation: 0,
    scale: 1,
    padding: [2, 4, 2, 4],
    backgroundFill: new Fill({ color: 'rgba(255, 255, 255, 0.8)' }),
    backgroundStroke: new Stroke({ color: '#ccc', width: 1 })
  }),
  
  // 几何函数(可选,修改渲染的几何)
  geometry: function(feature) {
    return feature.getGeometry();
  },
  
  // 渲染时机
  zIndex: 0
});

8.3.2 点样式

// 圆形点
const circleStyle = new Style({
  image: new Circle({
    radius: 10,
    fill: new Fill({ color: 'red' }),
    stroke: new Stroke({ color: 'white', width: 2 })
  })
});

// 图标点
const iconStyle = new Style({
  image: new Icon({
    src: '/images/marker.png',
    anchor: [0.5, 1],           // 锚点 [0-1, 0-1]
    anchorXUnits: 'fraction',   // 锚点单位: fraction, pixels
    anchorYUnits: 'fraction',
    scale: 1,
    rotation: 0,
    opacity: 1,
    size: [32, 32],             // 图标大小
    offset: [0, 0],             // 偏移
    color: 'red'                // 着色
  })
});

// SVG 图标
const svgStyle = new Style({
  image: new Icon({
    src: 'data:image/svg+xml,' + encodeURIComponent(`
      <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
        <circle cx="12" cy="12" r="10" fill="red"/>
      </svg>
    `),
    scale: 1
  })
});

// 规则形状
const shapeStyle = new Style({
  image: new RegularShape({
    points: 5,           // 顶点数(5 = 五角星)
    radius: 10,          // 外半径
    radius2: 5,          // 内半径(用于星形)
    angle: 0,            // 旋转角度
    fill: new Fill({ color: 'gold' }),
    stroke: new Stroke({ color: 'orange', width: 2 }),
    rotation: Math.PI    // 旋转
  })
});

// 三角形
const triangleStyle = new Style({
  image: new RegularShape({
    points: 3,
    radius: 10,
    fill: new Fill({ color: 'green' })
  })
});

// 正方形
const squareStyle = new Style({
  image: new RegularShape({
    points: 4,
    radius: 10,
    angle: Math.PI / 4,
    fill: new Fill({ color: 'blue' })
  })
});

8.3.3 线样式

// 实线
const solidLine = new Style({
  stroke: new Stroke({
    color: '#4285F4',
    width: 3
  })
});

// 虚线
const dashedLine = new Style({
  stroke: new Stroke({
    color: '#FF5722',
    width: 2,
    lineDash: [10, 5]  // [实线长度, 空白长度]
  })
});

// 点划线
const dotDashLine = new Style({
  stroke: new Stroke({
    color: '#9C27B0',
    width: 2,
    lineDash: [1, 5, 10, 5]
  })
});

// 渐变线(使用多个样式)
function createGradientLineStyle(feature) {
  const geometry = feature.getGeometry();
  const styles = [];
  
  // 底层宽线
  styles.push(new Style({
    stroke: new Stroke({
      color: 'rgba(0, 0, 0, 0.3)',
      width: 6
    })
  }));
  
  // 顶层细线
  styles.push(new Style({
    stroke: new Stroke({
      color: '#4285F4',
      width: 3
    })
  }));
  
  return styles;
}

// 带箭头的线
function createArrowLineStyle(feature) {
  const geometry = feature.getGeometry();
  const coords = geometry.getCoordinates();
  const styles = [];
  
  // 线样式
  styles.push(new Style({
    stroke: new Stroke({
      color: '#4285F4',
      width: 2
    })
  }));
  
  // 计算线段方向并添加箭头
  for (let i = 0; i < coords.length - 1; i++) {
    const start = coords[i];
    const end = coords[i + 1];
    const dx = end[0] - start[0];
    const dy = end[1] - start[1];
    const rotation = Math.atan2(dy, dx);
    
    // 在中点添加箭头
    const mid = [(start[0] + end[0]) / 2, (start[1] + end[1]) / 2];
    
    styles.push(new Style({
      geometry: new Point(mid),
      image: new RegularShape({
        points: 3,
        radius: 8,
        rotation: -rotation + Math.PI / 2,
        fill: new Fill({ color: '#4285F4' })
      })
    }));
  }
  
  return styles;
}

8.3.4 面样式

// 纯色填充
const solidFill = new Style({
  fill: new Fill({
    color: 'rgba(66, 133, 244, 0.5)'
  }),
  stroke: new Stroke({
    color: '#4285F4',
    width: 2
  })
});

// 图案填充
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = 10;
canvas.height = 10;

// 创建斜线图案
context.strokeStyle = '#4285F4';
context.lineWidth = 1;
context.beginPath();
context.moveTo(0, 10);
context.lineTo(10, 0);
context.stroke();

const patternFill = new Style({
  fill: new Fill({
    color: context.createPattern(canvas, 'repeat')
  }),
  stroke: new Stroke({
    color: '#4285F4',
    width: 2
  })
});

// 带标签的面
const labeledPolygon = new Style({
  fill: new Fill({
    color: 'rgba(66, 133, 244, 0.3)'
  }),
  stroke: new Stroke({
    color: '#4285F4',
    width: 2
  }),
  text: new Text({
    text: '区域名称',
    font: 'bold 14px sans-serif',
    fill: new Fill({ color: '#333' }),
    overflow: true
  })
});

8.4 动态样式

8.4.1 样式函数

// 基于属性的样式
function attributeBasedStyle(feature, resolution) {
  const type = feature.get('type');
  
  const styleMap = {
    school: new Style({
      image: new Circle({
        radius: 8,
        fill: new Fill({ color: '#FF5722' })
      })
    }),
    hospital: new Style({
      image: new Circle({
        radius: 8,
        fill: new Fill({ color: '#E91E63' })
      })
    }),
    park: new Style({
      image: new Circle({
        radius: 8,
        fill: new Fill({ color: '#4CAF50' })
      })
    })
  };
  
  return styleMap[type] || new Style({
    image: new Circle({
      radius: 6,
      fill: new Fill({ color: '#9E9E9E' })
    })
  });
}

// 基于分辨率的样式
function resolutionBasedStyle(feature, resolution) {
  const zoom = Math.log2(156543.03392804097 / resolution);
  
  // 高缩放级别显示详细样式
  if (zoom >= 15) {
    return new Style({
      image: new Circle({
        radius: 12,
        fill: new Fill({ color: '#4285F4' }),
        stroke: new Stroke({ color: '#fff', width: 2 })
      }),
      text: new Text({
        text: feature.get('name'),
        font: '12px sans-serif',
        offsetY: -20
      })
    });
  }
  
  // 低缩放级别显示简单样式
  return new Style({
    image: new Circle({
      radius: 6,
      fill: new Fill({ color: '#4285F4' })
    })
  });
}

// 基于状态的样式
function stateBasedStyle(feature, resolution) {
  const selected = feature.get('selected');
  const hovered = feature.get('hovered');
  
  if (selected) {
    return new Style({
      image: new Circle({
        radius: 12,
        fill: new Fill({ color: '#F44336' }),
        stroke: new Stroke({ color: '#fff', width: 3 })
      })
    });
  }
  
  if (hovered) {
    return new Style({
      image: new Circle({
        radius: 10,
        fill: new Fill({ color: '#FF9800' }),
        stroke: new Stroke({ color: '#fff', width: 2 })
      })
    });
  }
  
  return new Style({
    image: new Circle({
      radius: 8,
      fill: new Fill({ color: '#4285F4' }),
      stroke: new Stroke({ color: '#fff', width: 2 })
    })
  });
}

8.4.2 样式缓存

// 样式缓存管理器
class StyleCache {
  constructor() {
    this.cache = new Map();
  }
  
  getKey(type, color, size) {
    return `${type}-${color}-${size}`;
  }
  
  getStyle(type, color, size) {
    const key = this.getKey(type, color, size);
    
    if (!this.cache.has(key)) {
      this.cache.set(key, this.createStyle(type, color, size));
    }
    
    return this.cache.get(key);
  }
  
  createStyle(type, color, size) {
    return new Style({
      image: new Circle({
        radius: size,
        fill: new Fill({ color: color })
      })
    });
  }
  
  clear() {
    this.cache.clear();
  }
}

const styleCache = new StyleCache();

// 使用缓存的样式函数
function cachedStyleFunction(feature, resolution) {
  const type = feature.get('type');
  const color = typeColorMap[type] || '#999';
  const size = Math.max(4, 10 - resolution / 100);
  
  return styleCache.getStyle(type, color, Math.round(size));
}

8.4.3 动画样式

// 闪烁效果
function createBlinkingStyle(feature, resolution) {
  const time = Date.now();
  const blink = Math.sin(time / 200) > 0;
  
  return new Style({
    image: new Circle({
      radius: blink ? 10 : 6,
      fill: new Fill({ color: blink ? '#FF0000' : '#FF6666' })
    })
  });
}

// 需要持续渲染
map.on('postrender', () => {
  map.render();
});

// 脉冲效果
function createPulseStyle(feature, resolution) {
  const startTime = feature.get('pulseStart') || Date.now();
  const elapsed = Date.now() - startTime;
  const duration = 1000;
  const progress = (elapsed % duration) / duration;
  
  const radius = 5 + progress * 15;
  const opacity = 1 - progress;
  
  return [
    // 脉冲圈
    new Style({
      image: new Circle({
        radius: radius,
        stroke: new Stroke({
          color: `rgba(255, 0, 0, ${opacity})`,
          width: 2
        })
      })
    }),
    // 中心点
    new Style({
      image: new Circle({
        radius: 5,
        fill: new Fill({ color: 'red' })
      })
    })
  ];
}

// 轨迹动画样式
class TrackAnimator {
  constructor(layer, duration = 2000) {
    this.layer = layer;
    this.duration = duration;
    this.startTime = null;
    this.animating = false;
  }
  
  start() {
    this.startTime = Date.now();
    this.animating = true;
    this.animate();
  }
  
  stop() {
    this.animating = false;
  }
  
  animate() {
    if (!this.animating) return;
    
    const elapsed = Date.now() - this.startTime;
    const progress = (elapsed % this.duration) / this.duration;
    
    this.layer.setStyle((feature) => {
      const geometry = feature.getGeometry();
      if (geometry.getType() === 'LineString') {
        return this.createTrackStyle(geometry, progress);
      }
      return null;
    });
    
    requestAnimationFrame(() => this.animate());
  }
  
  createTrackStyle(geometry, progress) {
    const coords = geometry.getCoordinates();
    const totalLength = coords.length - 1;
    const currentIndex = Math.floor(progress * totalLength);
    
    // 已走过的路径
    const passedCoords = coords.slice(0, currentIndex + 1);
    
    return [
      // 完整路径(浅色)
      new Style({
        stroke: new Stroke({
          color: 'rgba(66, 133, 244, 0.3)',
          width: 4
        })
      }),
      // 已走过路径(深色)
      new Style({
        geometry: new LineString(passedCoords),
        stroke: new Stroke({
          color: '#4285F4',
          width: 4
        })
      }),
      // 当前位置点
      new Style({
        geometry: new Point(coords[currentIndex]),
        image: new Circle({
          radius: 8,
          fill: new Fill({ color: '#4285F4' }),
          stroke: new Stroke({ color: '#fff', width: 2 })
        })
      })
    ];
  }
}

8.5 标注与文本

8.5.1 文本样式详解

import { Text, Fill, Stroke, Style } from 'ol/style';

// 完整文本样式
const textStyle = new Text({
  // 文本内容
  text: '标签文本',
  
  // 字体
  font: 'bold 14px "Microsoft YaHei", sans-serif',
  
  // 填充颜色
  fill: new Fill({
    color: '#333333'
  }),
  
  // 描边(轮廓)
  stroke: new Stroke({
    color: '#ffffff',
    width: 3
  }),
  
  // 偏移
  offsetX: 0,
  offsetY: -20,
  
  // 对齐
  textAlign: 'center',      // 'left', 'center', 'right', 'start', 'end'
  textBaseline: 'middle',   // 'top', 'middle', 'bottom', 'alphabetic', 'hanging'
  
  // 旋转
  rotation: 0,
  
  // 缩放
  scale: 1,
  
  // 内边距
  padding: [2, 4, 2, 4],    // [top, right, bottom, left]
  
  // 背景
  backgroundFill: new Fill({
    color: 'rgba(255, 255, 255, 0.8)'
  }),
  backgroundStroke: new Stroke({
    color: '#cccccc',
    width: 1
  }),
  
  // 溢出处理
  overflow: false,          // true 允许文本溢出
  
  // 最大角度(用于沿线标注)
  maxAngle: Math.PI / 4,
  
  // 放置策略
  placement: 'point'        // 'point' 或 'line'
});

8.5.2 沿线标注

// 沿线标注样式
function lineLabel(feature) {
  return new Style({
    stroke: new Stroke({
      color: '#4285F4',
      width: 3
    }),
    text: new Text({
      text: feature.get('name'),
      font: '12px sans-serif',
      placement: 'line',
      fill: new Fill({ color: '#333' }),
      stroke: new Stroke({ color: '#fff', width: 3 }),
      maxAngle: Math.PI / 6,
      overflow: true
    })
  });
}

// 多行文本
function multiLineText(feature) {
  const name = feature.get('name');
  const type = feature.get('type');
  const text = `${name}\n(${type})`;
  
  return new Style({
    image: new Circle({
      radius: 8,
      fill: new Fill({ color: '#4285F4' })
    }),
    text: new Text({
      text: text,
      font: '12px sans-serif',
      fill: new Fill({ color: '#333' }),
      stroke: new Stroke({ color: '#fff', width: 2 }),
      offsetY: -25,
      textAlign: 'center'
    })
  });
}

8.5.3 标注避让

import VectorLayer from 'ol/layer/Vector';

// 启用标注避让
const labelLayer = new VectorLayer({
  source: vectorSource,
  declutter: true,  // 启用避让
  style: function(feature) {
    return new Style({
      image: new Circle({
        radius: 6,
        fill: new Fill({ color: '#4285F4' })
      }),
      text: new Text({
        text: feature.get('name'),
        font: '12px sans-serif',
        offsetY: -15,
        fill: new Fill({ color: '#333' }),
        stroke: new Stroke({ color: '#fff', width: 2 })
      })
    });
  }
});

// 自定义优先级
function prioritizedLabelStyle(feature) {
  const priority = feature.get('priority') || 0;
  
  return new Style({
    image: new Circle({
      radius: 6,
      fill: new Fill({ color: '#4285F4' })
    }),
    text: new Text({
      text: feature.get('name'),
      font: '12px sans-serif',
      offsetY: -15,
      fill: new Fill({ color: '#333' }),
      stroke: new Stroke({ color: '#fff', width: 2 })
    }),
    zIndex: priority  // 优先级高的先渲染
  });
}

8.6 实战示例

8.6.1 专题地图样式

// 分级设色(等级符号)
function choroplethStyle(feature) {
  const value = feature.get('population');
  
  let color;
  if (value > 20000000) {
    color = '#800026';
  } else if (value > 10000000) {
    color = '#BD0026';
  } else if (value > 5000000) {
    color = '#E31A1C';
  } else if (value > 1000000) {
    color = '#FC4E2A';
  } else if (value > 500000) {
    color = '#FD8D3C';
  } else {
    color = '#FFEDA0';
  }
  
  return new Style({
    fill: new Fill({ color: color }),
    stroke: new Stroke({ color: '#fff', width: 1 }),
    text: new Text({
      text: feature.get('name'),
      font: '12px sans-serif',
      fill: new Fill({ color: '#333' })
    })
  });
}

// 比例符号
function proportionalSymbolStyle(feature) {
  const value = feature.get('value');
  const maxValue = 100;
  const minRadius = 5;
  const maxRadius = 30;
  
  const radius = minRadius + (value / maxValue) * (maxRadius - minRadius);
  
  return new Style({
    image: new Circle({
      radius: radius,
      fill: new Fill({ color: 'rgba(66, 133, 244, 0.6)' }),
      stroke: new Stroke({ color: '#4285F4', width: 2 })
    }),
    text: new Text({
      text: value.toString(),
      font: 'bold 12px sans-serif',
      fill: new Fill({ color: '#fff' })
    })
  });
}

// 点密度图
function dotDensityStyle(feature) {
  const value = feature.get('population');
  const dotsPerUnit = 10000; // 每个点代表10000人
  const dotCount = Math.floor(value / dotsPerUnit);
  
  const geometry = feature.getGeometry();
  const extent = geometry.getExtent();
  const styles = [];
  
  // 底色
  styles.push(new Style({
    fill: new Fill({ color: 'rgba(255, 255, 255, 0.8)' }),
    stroke: new Stroke({ color: '#ccc', width: 1 })
  }));
  
  // 生成随机点
  for (let i = 0; i < dotCount; i++) {
    const x = extent[0] + Math.random() * (extent[2] - extent[0]);
    const y = extent[1] + Math.random() * (extent[3] - extent[1]);
    
    if (geometry.intersectsCoordinate([x, y])) {
      styles.push(new Style({
        geometry: new Point([x, y]),
        image: new Circle({
          radius: 2,
          fill: new Fill({ color: '#333' })
        })
      }));
    }
  }
  
  return styles;
}

8.6.2 样式管理器

class StyleManager {
  constructor() {
    this.styles = new Map();
    this.activeStyle = 'default';
  }
  
  register(name, styleFunction) {
    this.styles.set(name, styleFunction);
  }
  
  get(name) {
    return this.styles.get(name);
  }
  
  setActive(name) {
    if (this.styles.has(name)) {
      this.activeStyle = name;
    }
  }
  
  getActiveStyle() {
    return this.styles.get(this.activeStyle);
  }
  
  apply(layer) {
    layer.setStyle(this.getActiveStyle());
  }
}

// 使用示例
const styleManager = new StyleManager();

styleManager.register('default', defaultStyle);
styleManager.register('highlight', highlightStyle);
styleManager.register('choropleth', choroplethStyle);

// 切换样式
styleManager.setActive('choropleth');
styleManager.apply(vectorLayer);

8.7 本章小结

本章详细介绍了矢量数据与样式系统:

  1. Feature 要素:创建、操作、事件
  2. Geometry 几何:类型、操作、工具函数
  3. Style 样式:填充、描边、点样式、文本
  4. 动态样式:样式函数、缓存、动画
  5. 标注文本:文本样式、沿线标注、避让
  6. 实战示例:专题地图、样式管理

关键要点

  • 理解 Feature、Geometry、Style 的关系
  • 掌握各种几何类型的创建和操作
  • 熟练使用样式函数实现动态效果
  • 合理使用样式缓存优化性能

下一步

在下一章中,我们将详细学习栅格数据与瓦片服务,包括:

  • 瓦片原理
  • 自定义瓦片网格
  • 瓦片缓存
  • 瓦片预加载

← 上一章:Source数据源详解 | 返回目录 | 下一章:栅格数据与瓦片服务 →

posted @ 2026-01-08 11:37  我才是银古  阅读(16)  评论(0)    收藏  举报