OpenLayers 6 在指定多边形区域划定的范围内修改多边形(源码hack)

今天又被问到一个需求:有两个多边形,一个大多边形作为不规则范围限定里面的小多边形顶点范围,在修改小多边形的时候不超过大多边形的边界。

分析

起初想了很多办法来捕捉鼠标指针超越边界的情况,包括:

function callback(event)
{}
 
modify.on('modifystart',function(evt){
    map.on('pointermove',callback)
})
 
modify.on('modifyend',function(evt){
    map.un('pointermove',callback)
})

都限制不住快速移动鼠标时的情况。

考虑到interaction类靠事件驱动,想要在外面做文章可能难度比较大,只能考虑通过重载(派生、继承)原有的Modify并且修改拖动时的逻辑来实现。
实现

涉及到的类:ol/interaction/Modify

看了一下源码,还比较简单,直接顾名思义就找到了handleDragEvent这个接口:

  /**
   * @inheritDoc
   */
  handleDragEvent(evt) {
    this.ignoreNextSingleClick_ = false;
    this.willModifyFeatures_(evt);
 
    const vertex = evt.coordinate;
    for (let i = 0, ii = this.dragSegments_.length; i < ii; ++i) {
      const dragSegment = this.dragSegments_[i];
      const segmentData = dragSegment[0];
      const depth = segmentData.depth;
      const geometry = segmentData.geometry;
      let coordinates;
      const segment = segmentData.segment;
      const index = dragSegment[1];
 
      while (vertex.length < geometry.getStride()) {
        vertex.push(segment[index][vertex.length]);
      }
 
      switch (geometry.getType()) {
        case GeometryType.POINT:
          coordinates = vertex;
          segment[0] = segment[1] = vertex;
          break;
        case GeometryType.MULTI_POINT:
          coordinates = geometry.getCoordinates();
          coordinates[segmentData.index] = vertex;
          segment[0] = segment[1] = vertex;
          break;
        case GeometryType.LINE_STRING:
          coordinates = geometry.getCoordinates();
          coordinates[segmentData.index + index] = vertex;
          segment[index] = vertex;
          break;
        case GeometryType.MULTI_LINE_STRING:
          coordinates = geometry.getCoordinates();
          coordinates[depth[0]][segmentData.index + index] = vertex;
          segment[index] = vertex;
          break;
        case GeometryType.POLYGON:
          coordinates = geometry.getCoordinates();
          coordinates[depth[0]][segmentData.index + index] = vertex;
          segment[index] = vertex;
          break;
        case GeometryType.MULTI_POLYGON:
          coordinates = geometry.getCoordinates();
          coordinates[depth[1]][depth[0]][segmentData.index + index] = vertex;
          segment[index] = vertex;
          break;
        case GeometryType.CIRCLE:
          segment[0] = segment[1] = vertex;
          if (segmentData.index === CIRCLE_CENTER_INDEX) {
            this.changingFeature_ = true;
            geometry.setCenter(vertex);
            this.changingFeature_ = false;
          } else { // We're dragging the circle's circumference:
            this.changingFeature_ = true;
            geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex));
            this.changingFeature_ = false;
          }
          break;
        default:
          // pass
      }
 
      if (coordinates) {
        this.setGeometryCoordinates_(geometry, coordinates);
      }
    }
    this.createOrUpdateVertexFeature_(vertex);
  }

罗里吧嗦的一大堆,其实我们需要做的就是在它之前判断鼠标的点是否在我们划定的多边形之内,那么就必须在这个自定义的Modify里面加一个属性constrainGeom来获取我们用来做限制的几何区域,于是我们先把这个类写成这样:

class ModifyConstrain extends Modify {
        constructor(options) {
                super(options);
                this.constrainGeom_ = options.constrainGeom;
        }
}

然后想一下当我们的鼠标拖拽时超出了这个区域怎么处理,handleDragEvent实质上就是根据鼠标的位置来进行对修改结果的构造,那么不妨当鼠标位置超出区域的时候,强行修改这个坐标为区域边界的一点,为了方便计算,我使用了getClosestPoint来取这个点。判断鼠标坐标是否在区域内就很简单了,用的是intersectsCoordinate,于是这个类就变成了这样:

class ModifyConstrain extends Modify {
        constructor(options) {
                super(options);
                this.constrainGeom_ = options.constrainGeom;
        }
        handleDragEvent(evt) {
                if (!this.constrainGeom_.intersectsCoordinate(evt.coordinate))
                        evt.coordinate = this.constrainGeom_.getClosestPoint(evt.coordinate)
                super.handleDragEvent(evt)
        }
}

这样子就差不多了。下面附上测试源码:

import { Map, View } from 'ol';
import TileLayer from 'ol/layer/Tile';
import XYZ from 'ol/source/XYZ';
import VectorSource from 'ol/source/Vector';
import VectorLayer from 'ol/layer/Vector';
import * as turf from '@turf/turf'
import GeoJSON from 'ol/format/GeoJSON'
import { defaults as defaultInteractions, Modify, Select } from 'ol/interaction';
 
class ModifyConstrain extends Modify {
        constructor(options) {
                super(options);
                this.constrainGeom_ = options.constrainGeom;
        }
        handleDragEvent(evt) {
                if (!this.constrainGeom_.intersectsCoordinate(evt.coordinate))
                        evt.coordinate = this.constrainGeom_.getClosestPoint(evt.coordinate)
                super.handleDragEvent(evt)
        }
}
 
var key='你自己的googleKey'
 
let tileLayer = new TileLayer({
        source: new XYZ({
                url: 'http://www.google.cn/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i451159038!3m14!2szh-CN!3sUS!5e18!12m1!1e68!12m3!1e37!2m1!1ssmartmaps!12m4!1e26!2m2!1sstyles!2zcC5oOiNmZjFhMDB8cC5pbDp0cnVlfHAuczotMTAwfHAubDozM3xwLmc6MC41LHMudDo2fHMuZTpnfHAuYzojZmYyRDMzM0M!4e0&'+
key+'&token=126219'
        })
})
var vSource = new VectorSource()
var vLayer = new VectorLayer(
        {
                source: vSource
        }
)
var turfFormat = new GeoJSON();
var poly = turf.polygon([[[0, 29], [3.5, 29], [2.5, 32], [0, 29]]]);
var scaledPoly = turf.transformScale(poly, 3);
var featureExtent = turfFormat.readFeature(scaledPoly)
var featurePolygon = turfFormat.readFeature(poly)
vSource.addFeature(featureExtent)
vSource.addFeature(featurePolygon)
var select = new Select({
        wrapX: false
});
 
var polygonModify = new ModifyConstrain({
        features: select.getFeatures(),
        constrainGeom: featureExtent.getGeometry()
});
 
let map = new Map({
        interactions: defaultInteractions().extend([select, polygonModify]),
        target: 'map',
        layers: [
                tileLayer
        ],
        view: new View({
                center: [4.673, 28.148],
                zoom: 6,
                projection: "EPSG:4326"
        })
});
map.addLayer(vLayer)

一点感想

不要怕源码,源码是我们的好朋友