原文链接及内容

运行界面

一个使用ol/interaction/Modify交互来缩放和旋转几何图形的例子。自定义样式函数会根据被修改顶点的位置生成并显示原始几何图形的缩放和旋转版本,即交互结束时的最终几何体。默认情况下,ol/geom/Geometry缩放和旋转方法使用几何范围的中心作为锚点。对于不规则形状,随着几何形状的旋转,范围会发生变化,如果停止旋转和恢复旋转,使用其中心作为锚会产生不同的结果。为了避免这种情况,对于ol/geom/Polygon,我们使用了一个相对于几何体固定的锚点,锚点是顶点的重心,对于ol/geom/LineString,锚点是中点。只有外部顶点(距离锚点最大距离的1/3以上)用于缩放和旋转,因为接近锚点的精确缩放将是困难的。为了方便用户,样式函数突出显示锚点和可用顶点。ol/interaction/Translate交互作用也可用于重新定位几何图形。ModifyTranslate交互具有互斥的条件选项,因此它们可以一起使用。使用Ctrl+拖动(Mac上为Command+拖动)来使用平移交互。

main.js代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
import Map from 'ol/Map.js';
import View from 'ol/View.js';
import {Circle as CircleStyle, Fill, Stroke, Style} from 'ol/style.js';
import {Draw, Modify, Translate} from 'ol/interaction.js';
import {MultiPoint, Point} from 'ol/geom.js';
import {OSM, Vector as VectorSource} from 'ol/source.js';
import {Tile as TileLayer, Vector as VectorLayer} from 'ol/layer.js';
import {getCenter, getHeight, getWidth} from 'ol/extent.js';
import {
never,
platformModifierKeyOnly,
primaryAction,
} from 'ol/events/condition.js';

const raster = new TileLayer({
source: new OSM(),
});

const source = new VectorSource();

const style = new Style({
geometry: function (feature) {
const modifyGeometry = feature.get('modifyGeometry');
return modifyGeometry ? modifyGeometry.geometry : feature.getGeometry();
},
fill: new Fill({
color: 'rgba(255, 255, 255, 0.2)',
}),
stroke: new Stroke({
color: '#ffcc33',
width: 2,
}),
image: new CircleStyle({
radius: 7,
fill: new Fill({
color: '#ffcc33',
}),
}),
});

function calculateCenter(geometry) {
let center, coordinates, minRadius;
const type = geometry.getType();
if (type === 'Polygon') {
let x = 0;
let y = 0;
let i = 0;
coordinates = geometry.getCoordinates()[0].slice(1);
coordinates.forEach(function (coordinate) {
x += coordinate[0];
y += coordinate[1];
i++;
});
center = [x / i, y / i];
} else if (type === 'LineString') {
center = geometry.getCoordinateAt(0.5);
coordinates = geometry.getCoordinates();
} else {
center = getCenter(geometry.getExtent());
}
let sqDistances;
if (coordinates) {
sqDistances = coordinates.map(function (coordinate) {
const dx = coordinate[0] - center[0];
const dy = coordinate[1] - center[1];
return dx * dx + dy * dy;
});
minRadius = Math.sqrt(Math.max.apply(Math, sqDistances)) / 3;
} else {
minRadius =
Math.max(
getWidth(geometry.getExtent()),
getHeight(geometry.getExtent())
) / 3;
}
return {
center: center,
coordinates: coordinates,
minRadius: minRadius,
sqDistances: sqDistances,
};
}

const vector = new VectorLayer({
source: source,
style: function (feature) {
const styles = [style];
const modifyGeometry = feature.get('modifyGeometry');
const geometry = modifyGeometry
? modifyGeometry.geometry
: feature.getGeometry();
const result = calculateCenter(geometry);
const center = result.center;
if (center) {
styles.push(
new Style({
geometry: new Point(center),
image: new CircleStyle({
radius: 4,
fill: new Fill({
color: '#ff3333',
}),
}),
})
);
const coordinates = result.coordinates;
if (coordinates) {
const minRadius = result.minRadius;
const sqDistances = result.sqDistances;
const rsq = minRadius * minRadius;
const points = coordinates.filter(function (coordinate, index) {
return sqDistances[index] > rsq;
});
styles.push(
new Style({
geometry: new MultiPoint(points),
image: new CircleStyle({
radius: 4,
fill: new Fill({
color: '#33cc33',
}),
}),
})
);
}
}
return styles;
},
});

const map = new Map({
layers: [raster, vector],
target: 'map',
view: new View({
center: [-11000000, 4600000],
zoom: 4,
}),
});

const defaultStyle = new Modify({source: source})
.getOverlay()
.getStyleFunction();

const modify = new Modify({
source: source,
condition: function (event) {
return primaryAction(event) && !platformModifierKeyOnly(event);
},
deleteCondition: never,
insertVertexCondition: never,
style: function (feature) {
feature.get('features').forEach(function (modifyFeature) {
const modifyGeometry = modifyFeature.get('modifyGeometry');
if (modifyGeometry) {
const point = feature.getGeometry().getCoordinates();
let modifyPoint = modifyGeometry.point;
if (!modifyPoint) {
// save the initial geometry and vertex position
modifyPoint = point;
modifyGeometry.point = modifyPoint;
modifyGeometry.geometry0 = modifyGeometry.geometry;
// get anchor and minimum radius of vertices to be used
const result = calculateCenter(modifyGeometry.geometry0);
modifyGeometry.center = result.center;
modifyGeometry.minRadius = result.minRadius;
}

const center = modifyGeometry.center;
const minRadius = modifyGeometry.minRadius;
let dx, dy;
dx = modifyPoint[0] - center[0];
dy = modifyPoint[1] - center[1];
const initialRadius = Math.sqrt(dx * dx + dy * dy);
if (initialRadius > minRadius) {
const initialAngle = Math.atan2(dy, dx);
dx = point[0] - center[0];
dy = point[1] - center[1];
const currentRadius = Math.sqrt(dx * dx + dy * dy);
if (currentRadius > 0) {
const currentAngle = Math.atan2(dy, dx);
const geometry = modifyGeometry.geometry0.clone();
geometry.scale(currentRadius / initialRadius, undefined, center);
geometry.rotate(currentAngle - initialAngle, center);
modifyGeometry.geometry = geometry;
}
}
}
});
return defaultStyle(feature);
},
});

modify.on('modifystart', function (event) {
event.features.forEach(function (feature) {
feature.set(
'modifyGeometry',
{geometry: feature.getGeometry().clone()},
true
);
});
});

modify.on('modifyend', function (event) {
event.features.forEach(function (feature) {
const modifyGeometry = feature.get('modifyGeometry');
if (modifyGeometry) {
feature.setGeometry(modifyGeometry.geometry);
feature.unset('modifyGeometry', true);
}
});
});

map.addInteraction(modify);
map.addInteraction(
new Translate({
condition: function (event) {
return primaryAction(event) && platformModifierKeyOnly(event);
},
layers: [vector],
})
);

let draw; // global so we can remove it later
const typeSelect = document.getElementById('type');

function addInteractions() {
draw = new Draw({
source: source,
type: typeSelect.value,
});
map.addInteraction(draw);
}

/**
* Handle change event.
*/
typeSelect.onchange = function () {
map.removeInteraction(draw);
addInteractions();
};

addInteractions();

界面布局文件index.html代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Scale and Rotate using Modify Interaction</title>
<link rel="stylesheet" href="node_modules/ol/ol.css">
<style>
.map {
width: 100%;
height: 400px;
}
</style>
</head>
<body>
<div id="map" class="map"></div>
<form>
<label for="type">Geometry type &nbsp;</label>
<select id="type">
<option value="Point">Point</option>
<option value="LineString">LineString</option>
<option value="Polygon" selected>Polygon</option>
<option value="Circle">Circle</option>
</select>
</form>
<!-- Pointer events polyfill for old browsers, see https://caniuse.com/#feat=pointer -->
<script src="https://cdn.jsdelivr.net/npm/elm-pep@1.0.6/dist/elm-pep.js"></script>
<script type="module" src="main.js"></script>
</body>
</html>