天天看點

OpenLayers 6 在指定多邊形區域劃定的範圍内修改多邊形(源碼hack)

今天又被問到一個需求:有兩個多邊形,一個大多邊形作為不規則範圍限定裡面的小多邊形頂點範圍,在修改小多邊形的時候不超過大多邊形的邊界。

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)
           

一點感想

不要怕源碼,源碼是我們的好朋友

繼續閱讀