OpenLayers 6 在指定多邊形區(qū)域劃定的范圍內(nèi)修改多邊形(源碼hack)
今天又被問到一個需求:有兩個多邊形,一個大多邊形作為不規(guī)則范圍限定里面的小多邊形頂點(diǎn)范圍,在修改小多邊形的時候不超過大多邊形的邊界。
分析
起初想了很多辦法來捕捉鼠標(biāo)指針超越邊界的情況,包括:
function callback(event)
{}
modify.on('modifystart',function(evt){
map.on('pointermove',callback)
})
modify.on('modifyend',function(evt){
map.un('pointermove',callback)
})
都限制不住快速移動鼠標(biāo)時的情況。
考慮到interaction類靠事件驅(qū)動,想要在外面做文章可能難度比較大,只能考慮通過重載(派生、繼承)原有的Modify并且修改拖動時的邏輯來實(shí)現(xiàn)。
實(shí)現(xiàn)
涉及到的類: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);
}
羅里吧嗦的一大堆,其實(shí)我們需要做的就是在它之前判斷鼠標(biāo)的點(diǎn)是否在我們劃定的多邊形之內(nèi),那么就必須在這個自定義的Modify里面加一個屬性constrainGeom來獲取我們用來做限制的幾何區(qū)域,于是我們先把這個類寫成這樣:
class ModifyConstrain extends Modify {
constructor(options) {
super(options);
this.constrainGeom_ = options.constrainGeom;
}
}
然后想一下當(dāng)我們的鼠標(biāo)拖拽時超出了這個區(qū)域怎么處理,handleDragEvent實(shí)質(zhì)上就是根據(jù)鼠標(biāo)的位置來進(jìn)行對修改結(jié)果的構(gòu)造,那么不妨當(dāng)鼠標(biāo)位置超出區(qū)域的時候,強(qiáng)行修改這個坐標(biāo)為區(qū)域邊界的一點(diǎn),為了方便計(jì)算,我使用了getClosestPoint來取這個點(diǎn)。判斷鼠標(biāo)坐標(biāo)是否在區(qū)域內(nèi)就很簡單了,用的是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)
一點(diǎn)感想
不要怕源碼,源碼是我們的好朋友