第15章 - 要素编辑与绘制

第15章 - 要素编辑与绘制

15.1 绘制功能

15.1.1 基本绘制

import Draw from 'ol/interaction/Draw';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import { Style, Fill, Stroke, Circle } from 'ol/style';

// 创建编辑图层
const editSource = new VectorSource();
const editLayer = new VectorLayer({
  source: editSource,
  style: new Style({
    fill: new Fill({ color: 'rgba(66, 133, 244, 0.3)' }),
    stroke: new Stroke({ color: '#4285F4', width: 2 }),
    image: new Circle({
      radius: 6,
      fill: new Fill({ color: '#4285F4' })
    })
  })
});
map.addLayer(editLayer);

// 绘制点
const drawPoint = new Draw({ source: editSource, type: 'Point' });

// 绘制线
const drawLine = new Draw({ source: editSource, type: 'LineString' });

// 绘制面
const drawPolygon = new Draw({ source: editSource, type: 'Polygon' });

// 绘制圆
const drawCircle = new Draw({ source: editSource, type: 'Circle' });

15.1.2 特殊形状绘制

import { createBox, createRegularPolygon } from 'ol/interaction/Draw';

// 矩形
const drawBox = new Draw({
  source: editSource,
  type: 'Circle',
  geometryFunction: createBox()
});

// 正方形
const drawSquare = new Draw({
  source: editSource,
  type: 'Circle',
  geometryFunction: createRegularPolygon(4)
});

// 正六边形
const drawHexagon = new Draw({
  source: editSource,
  type: 'Circle',
  geometryFunction: createRegularPolygon(6)
});

// 自由绘制
const freehand = new Draw({
  source: editSource,
  type: 'LineString',
  freehand: true
});

15.1.3 绘制事件

const draw = new Draw({ source: editSource, type: 'Polygon' });

draw.on('drawstart', (event) => {
  console.log('开始绘制');
});

draw.on('drawend', (event) => {
  const feature = event.feature;
  const area = getArea(feature.getGeometry());
  console.log('绘制完成,面积:', area, '平方米');
});

draw.on('drawabort', () => {
  console.log('取消绘制');
});

// Esc 键取消绘制
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') {
    draw.abortDrawing();
  }
});

15.2 编辑功能

15.2.1 修改要素

import Modify from 'ol/interaction/Modify';
import Snap from 'ol/interaction/Snap';

// 修改所有要素
const modify = new Modify({ source: editSource });

// 修改选中要素
const select = new Select();
const modifySelected = new Modify({ features: select.getFeatures() });

// 捕捉
const snap = new Snap({ source: editSource });

// 添加交互(顺序重要)
map.addInteraction(select);
map.addInteraction(modifySelected);
map.addInteraction(snap);

// 事件
modify.on('modifyend', (event) => {
  console.log('修改完成');
});

15.2.2 删除要素

// 删除选中要素
function deleteSelected() {
  const features = select.getFeatures();
  features.forEach(feature => {
    editSource.removeFeature(feature);
  });
  features.clear();
}

// 按 Delete 键删除
document.addEventListener('keydown', (e) => {
  if (e.key === 'Delete') {
    deleteSelected();
  }
});

// 右键菜单删除
map.on('contextmenu', (event) => {
  event.preventDefault();
  const feature = map.forEachFeatureAtPixel(event.pixel, f => f);
  if (feature) {
    editSource.removeFeature(feature);
  }
});

15.2.3 平移要素

import Translate from 'ol/interaction/Translate';

// 平移选中要素
const translate = new Translate({
  features: select.getFeatures()
});

translate.on('translateend', (event) => {
  console.log('平移完成');
});

map.addInteraction(translate);

15.3 编辑工具栏

class EditToolbar {
  constructor(map, source) {
    this.map = map;
    this.source = source;
    this.activeInteraction = null;
    
    // 选择和修改交互
    this.select = new Select();
    this.modify = new Modify({ features: this.select.getFeatures() });
    this.snap = new Snap({ source });
    
    map.addInteraction(this.select);
    map.addInteraction(this.modify);
    map.addInteraction(this.snap);
    
    this.select.setActive(false);
    this.modify.setActive(false);
  }
  
  activateTool(type) {
    this.deactivate();
    
    if (type === 'select') {
      this.select.setActive(true);
      this.modify.setActive(true);
    } else if (['Point', 'LineString', 'Polygon', 'Circle'].includes(type)) {
      this.activeInteraction = new Draw({
        source: this.source,
        type: type
      });
      this.map.addInteraction(this.activeInteraction);
    }
  }
  
  deactivate() {
    this.select.setActive(false);
    this.modify.setActive(false);
    
    if (this.activeInteraction) {
      this.map.removeInteraction(this.activeInteraction);
      this.activeInteraction = null;
    }
  }
  
  deleteSelected() {
    const features = this.select.getFeatures();
    features.forEach(f => this.source.removeFeature(f));
    features.clear();
  }
  
  clear() {
    this.source.clear();
    this.select.getFeatures().clear();
  }
  
  undo() {
    // 实现撤销功能需要历史记录
  }
}

// 使用
const toolbar = new EditToolbar(map, editSource);

document.getElementById('draw-point').onclick = () => toolbar.activateTool('Point');
document.getElementById('draw-line').onclick = () => toolbar.activateTool('LineString');
document.getElementById('draw-polygon').onclick = () => toolbar.activateTool('Polygon');
document.getElementById('select').onclick = () => toolbar.activateTool('select');
document.getElementById('delete').onclick = () => toolbar.deleteSelected();
document.getElementById('clear').onclick = () => toolbar.clear();

15.4 测量功能

import { getLength, getArea } from 'ol/sphere';

class MeasureTool {
  constructor(map) {
    this.map = map;
    this.source = new VectorSource();
    this.layer = new VectorLayer({
      source: this.source,
      style: this.createStyle()
    });
    
    map.addLayer(this.layer);
    this.draw = null;
    this.tooltip = null;
  }
  
  measureDistance() {
    this.clear();
    this.draw = new Draw({ source: this.source, type: 'LineString' });
    
    this.draw.on('drawstart', (e) => {
      e.feature.on('change', () => {
        const geom = e.feature.getGeometry();
        const length = getLength(geom);
        this.updateTooltip(geom.getLastCoordinate(), this.formatLength(length));
      });
    });
    
    this.draw.on('drawend', () => {
      this.tooltip = null;
    });
    
    this.map.addInteraction(this.draw);
  }
  
  measureArea() {
    this.clear();
    this.draw = new Draw({ source: this.source, type: 'Polygon' });
    
    this.draw.on('drawstart', (e) => {
      e.feature.on('change', () => {
        const geom = e.feature.getGeometry();
        const area = getArea(geom);
        const coord = geom.getInteriorPoint().getCoordinates();
        this.updateTooltip(coord, this.formatArea(area));
      });
    });
    
    this.map.addInteraction(this.draw);
  }
  
  formatLength(length) {
    return length > 1000 ? 
      `${(length / 1000).toFixed(2)} km` : 
      `${length.toFixed(2)} m`;
  }
  
  formatArea(area) {
    return area > 1000000 ? 
      `${(area / 1000000).toFixed(2)} km²` : 
      `${area.toFixed(2)} m²`;
  }
  
  createStyle() {
    return new Style({
      fill: new Fill({ color: 'rgba(255, 255, 255, 0.2)' }),
      stroke: new Stroke({ color: '#ffcc33', width: 2 }),
      image: new Circle({
        radius: 5,
        fill: new Fill({ color: '#ffcc33' })
      })
    });
  }
  
  updateTooltip(coordinate, text) {
    // 创建或更新工具提示
    if (!this.tooltip) {
      this.tooltip = new Overlay({
        element: this.createTooltipElement(),
        offset: [0, -15],
        positioning: 'bottom-center'
      });
      this.map.addOverlay(this.tooltip);
    }
    
    this.tooltip.getElement().innerHTML = text;
    this.tooltip.setPosition(coordinate);
  }
  
  createTooltipElement() {
    const element = document.createElement('div');
    element.className = 'measure-tooltip';
    return element;
  }
  
  clear() {
    if (this.draw) {
      this.map.removeInteraction(this.draw);
      this.draw = null;
    }
    this.source.clear();
    if (this.tooltip) {
      this.map.removeOverlay(this.tooltip);
      this.tooltip = null;
    }
  }
}

15.5 数据导入导出

import GeoJSON from 'ol/format/GeoJSON';
import KML from 'ol/format/KML';

// 导出 GeoJSON
function exportGeoJSON(source) {
  const format = new GeoJSON();
  const features = source.getFeatures();
  
  const json = format.writeFeatures(features, {
    dataProjection: 'EPSG:4326',
    featureProjection: 'EPSG:3857'
  });
  
  const blob = new Blob([json], { type: 'application/json' });
  const url = URL.createObjectURL(blob);
  
  const link = document.createElement('a');
  link.href = url;
  link.download = 'features.geojson';
  link.click();
  
  URL.revokeObjectURL(url);
}

// 导入 GeoJSON
function importGeoJSON(source, file) {
  const reader = new FileReader();
  
  reader.onload = (e) => {
    const format = new GeoJSON();
    const features = format.readFeatures(e.target.result, {
      dataProjection: 'EPSG:4326',
      featureProjection: 'EPSG:3857'
    });
    
    source.addFeatures(features);
  };
  
  reader.readAsText(file);
}

// 文件拖放导入
map.getTargetElement().addEventListener('dragover', (e) => {
  e.preventDefault();
});

map.getTargetElement().addEventListener('drop', (e) => {
  e.preventDefault();
  const file = e.dataTransfer.files[0];
  if (file.name.endsWith('.geojson') || file.name.endsWith('.json')) {
    importGeoJSON(editSource, file);
  }
});

15.6 本章小结

本章介绍了要素编辑与绘制:

  1. 绘制功能:点、线、面、特殊形状
  2. 编辑功能:修改、删除、平移
  3. 编辑工具栏:工具管理
  4. 测量功能:距离、面积测量
  5. 数据导入导出:GeoJSON、KML

关键要点

  • Draw 交互实现绘制
  • Modify + Snap 实现编辑
  • 使用 GeoJSON 格式导入导出

← 上一章:投影与坐标转换 | 返回目录 | 下一章:OGC服务集成 →

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