TransformFeature.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  1. /* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the 2-clause BSD license.
  3. * See license.txt in the OpenLayers distribution or repository for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Control.js
  7. * @requires OpenLayers/Control/DragFeature.js
  8. * @requires OpenLayers/Feature/Vector.js
  9. * @requires OpenLayers/Geometry/LineString.js
  10. * @requires OpenLayers/Geometry/Point.js
  11. */
  12. /**
  13. * Class: OpenLayers.Control.TransformFeature
  14. * Control to transform features with a standard transformation box.
  15. *
  16. * Inherits From:
  17. * - <OpenLayers.Control>
  18. */
  19. OpenLayers.Control.TransformFeature = OpenLayers.Class(OpenLayers.Control, {
  20. /**
  21. * APIProperty: events
  22. * {<OpenLayers.Events>} Events instance for listeners and triggering
  23. * control specific events.
  24. *
  25. * Register a listener for a particular event with the following syntax:
  26. * (code)
  27. * control.events.register(type, obj, listener);
  28. * (end)
  29. *
  30. * Supported event types (in addition to those from <OpenLayers.Control.events>):
  31. * beforesetfeature - Triggered before a feature is set for
  32. * tranformation. The feature will not be set if a listener returns
  33. * false. Listeners receive a *feature* property, with the feature
  34. * that will be set for transformation. Listeners are allowed to
  35. * set the control's *scale*, *ratio* and *rotation* properties,
  36. * which will set the initial scale, ratio and rotation of the
  37. * feature, like the <setFeature> method's initialParams argument.
  38. * setfeature - Triggered when a feature is set for tranformation.
  39. * Listeners receive a *feature* property, with the feature that
  40. * is now set for transformation.
  41. * beforetransform - Triggered while dragging, before a feature is
  42. * transformed. The feature will not be transformed if a listener
  43. * returns false (but the box still will). Listeners receive one or
  44. * more of *center*, *scale*, *ratio* and *rotation*. The *center*
  45. * property is an <OpenLayers.Geometry.Point> object with the new
  46. * center of the transformed feature, the others are Floats with the
  47. * scale, ratio or rotation change since the last transformation.
  48. * transform - Triggered while dragging, when a feature is transformed.
  49. * Listeners receive an event object with one or more of *center*,
  50. * scale*, *ratio* and *rotation*. The *center* property is an
  51. * <OpenLayers.Geometry.Point> object with the new center of the
  52. * transformed feature, the others are Floats with the scale, ratio
  53. * or rotation change of the feature since the last transformation.
  54. * transformcomplete - Triggered after dragging. Listeners receive
  55. * an event object with the transformed *feature*.
  56. */
  57. /**
  58. * APIProperty: geometryTypes
  59. * {Array(String)} To restrict transformation to a limited set of geometry
  60. * types, send a list of strings corresponding to the geometry class
  61. * names.
  62. */
  63. geometryTypes: null,
  64. /**
  65. * Property: layer
  66. * {<OpenLayers.Layer.Vector>}
  67. */
  68. layer: null,
  69. /**
  70. * APIProperty: preserveAspectRatio
  71. * {Boolean} set to true to not change the feature's aspect ratio.
  72. */
  73. preserveAspectRatio: false,
  74. /**
  75. * APIProperty: rotate
  76. * {Boolean} set to false if rotation should be disabled. Default is true.
  77. * To be passed with the constructor or set when the control is not
  78. * active.
  79. */
  80. rotate: true,
  81. /**
  82. * APIProperty: feature
  83. * {<OpenLayers.Feature.Vector>} Feature currently available for
  84. * transformation. Read-only, use <setFeature> to set it manually.
  85. */
  86. feature: null,
  87. /**
  88. * APIProperty: renderIntent
  89. * {String|Object} Render intent for the transformation box and
  90. * handles. A symbolizer object can also be provided here.
  91. */
  92. renderIntent: "temporary",
  93. /**
  94. * APIProperty: rotationHandleSymbolizer
  95. * {Object|String} Optional. A custom symbolizer for the rotation handles.
  96. * A render intent can also be provided here. Defaults to
  97. * (code)
  98. * {
  99. * stroke: false,
  100. * pointRadius: 10,
  101. * fillOpacity: 0,
  102. * cursor: "pointer"
  103. * }
  104. * (end)
  105. */
  106. rotationHandleSymbolizer: null,
  107. /**
  108. * APIProperty: box
  109. * {<OpenLayers.Feature.Vector>} The transformation box rectangle.
  110. * Read-only.
  111. */
  112. box: null,
  113. /**
  114. * APIProperty: center
  115. * {<OpenLayers.Geometry.Point>} The center of the feature bounds.
  116. * Read-only.
  117. */
  118. center: null,
  119. /**
  120. * APIProperty: scale
  121. * {Float} The scale of the feature, relative to the scale the time the
  122. * feature was set. Read-only, except for *beforesetfeature*
  123. * listeners.
  124. */
  125. scale: 1,
  126. /**
  127. * APIProperty: ratio
  128. * {Float} The ratio of the feature relative to the ratio the time the
  129. * feature was set. Read-only, except for *beforesetfeature*
  130. * listeners.
  131. */
  132. ratio: 1,
  133. /**
  134. * Property: rotation
  135. * {Integer} the current rotation angle of the box. Read-only, except for
  136. * *beforesetfeature* listeners.
  137. */
  138. rotation: 0,
  139. /**
  140. * APIProperty: handles
  141. * {Array(<OpenLayers.Feature.Vector>)} The 8 handles currently available
  142. * for scaling/resizing. Numbered counterclockwise, starting from the
  143. * southwest corner. Read-only.
  144. */
  145. handles: null,
  146. /**
  147. * APIProperty: rotationHandles
  148. * {Array(<OpenLayers.Feature.Vector>)} The 4 rotation handles currently
  149. * available for rotating. Numbered counterclockwise, starting from
  150. * the southwest corner. Read-only.
  151. */
  152. rotationHandles: null,
  153. /**
  154. * Property: dragControl
  155. * {<OpenLayers.Control.DragFeature>}
  156. */
  157. dragControl: null,
  158. /**
  159. * APIProperty: irregular
  160. * {Boolean} Make scaling/resizing work irregularly. If true then
  161. * dragging a handle causes the feature to resize in the direction
  162. * of movement. If false then the feature resizes symetrically
  163. * about it's center.
  164. */
  165. irregular: false,
  166. /**
  167. * Constructor: OpenLayers.Control.TransformFeature
  168. * Create a new transform feature control.
  169. *
  170. * Parameters:
  171. * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
  172. * will be transformed.
  173. * options - {Object} Optional object whose properties will be set on the
  174. * control.
  175. */
  176. initialize: function(layer, options) {
  177. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  178. this.layer = layer;
  179. if(!this.rotationHandleSymbolizer) {
  180. this.rotationHandleSymbolizer = {
  181. stroke: false,
  182. pointRadius: 10,
  183. fillOpacity: 0,
  184. cursor: "pointer"
  185. };
  186. }
  187. this.createBox();
  188. this.createControl();
  189. },
  190. /**
  191. * APIMethod: activate
  192. * Activates the control.
  193. */
  194. activate: function() {
  195. var activated = false;
  196. if(OpenLayers.Control.prototype.activate.apply(this, arguments)) {
  197. this.dragControl.activate();
  198. this.layer.addFeatures([this.box]);
  199. this.rotate && this.layer.addFeatures(this.rotationHandles);
  200. this.layer.addFeatures(this.handles);
  201. activated = true;
  202. }
  203. return activated;
  204. },
  205. /**
  206. * APIMethod: deactivate
  207. * Deactivates the control.
  208. */
  209. deactivate: function() {
  210. var deactivated = false;
  211. if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
  212. this.layer.removeFeatures(this.handles);
  213. this.rotate && this.layer.removeFeatures(this.rotationHandles);
  214. this.layer.removeFeatures([this.box]);
  215. this.dragControl.deactivate();
  216. deactivated = true;
  217. }
  218. return deactivated;
  219. },
  220. /**
  221. * Method: setMap
  222. *
  223. * Parameters:
  224. * map - {<OpenLayers.Map>}
  225. */
  226. setMap: function(map) {
  227. this.dragControl.setMap(map);
  228. OpenLayers.Control.prototype.setMap.apply(this, arguments);
  229. },
  230. /**
  231. * APIMethod: setFeature
  232. * Place the transformation box on a feature and start transforming it.
  233. * If the control is not active, it will be activated.
  234. *
  235. * Parameters:
  236. * feature - {<OpenLayers.Feature.Vector>}
  237. * initialParams - {Object} Initial values for rotation, scale or ratio.
  238. * Setting a rotation value here will cause the transformation box to
  239. * start rotated. Setting a scale or ratio will not affect the
  240. * transormation box, but applications may use this to keep track of
  241. * scale and ratio of a feature across multiple transforms.
  242. */
  243. setFeature: function(feature, initialParams) {
  244. initialParams = OpenLayers.Util.applyDefaults(initialParams, {
  245. rotation: 0,
  246. scale: 1,
  247. ratio: 1
  248. });
  249. var oldRotation = this.rotation;
  250. var oldCenter = this.center;
  251. OpenLayers.Util.extend(this, initialParams);
  252. var cont = this.events.triggerEvent("beforesetfeature",
  253. {feature: feature}
  254. );
  255. if (cont === false) {
  256. return;
  257. }
  258. this.feature = feature;
  259. this.activate();
  260. this._setfeature = true;
  261. var featureBounds = this.feature.geometry.getBounds();
  262. this.box.move(featureBounds.getCenterLonLat());
  263. this.box.geometry.rotate(-oldRotation, oldCenter);
  264. this._angle = 0;
  265. var ll;
  266. if(this.rotation) {
  267. var geom = feature.geometry.clone();
  268. geom.rotate(-this.rotation, this.center);
  269. var box = new OpenLayers.Feature.Vector(
  270. geom.getBounds().toGeometry());
  271. box.geometry.rotate(this.rotation, this.center);
  272. this.box.geometry.rotate(this.rotation, this.center);
  273. this.box.move(box.geometry.getBounds().getCenterLonLat());
  274. var llGeom = box.geometry.components[0].components[0];
  275. ll = llGeom.getBounds().getCenterLonLat();
  276. } else {
  277. ll = new OpenLayers.LonLat(featureBounds.left, featureBounds.bottom);
  278. }
  279. this.handles[0].move(ll);
  280. delete this._setfeature;
  281. this.events.triggerEvent("setfeature", {feature: feature});
  282. },
  283. /**
  284. * APIMethod: unsetFeature
  285. * Remove the transformation box off any feature.
  286. * If the control is active, it will be deactivated first.
  287. */
  288. unsetFeature: function() {
  289. if (this.active) {
  290. this.deactivate();
  291. } else {
  292. this.feature = null;
  293. this.rotation = 0;
  294. this.scale = 1;
  295. this.ratio = 1;
  296. }
  297. },
  298. /**
  299. * Method: createBox
  300. * Creates the box with all handles and transformation handles.
  301. */
  302. createBox: function() {
  303. var control = this;
  304. this.center = new OpenLayers.Geometry.Point(0, 0);
  305. this.box = new OpenLayers.Feature.Vector(
  306. new OpenLayers.Geometry.LineString([
  307. new OpenLayers.Geometry.Point(-1, -1),
  308. new OpenLayers.Geometry.Point(0, -1),
  309. new OpenLayers.Geometry.Point(1, -1),
  310. new OpenLayers.Geometry.Point(1, 0),
  311. new OpenLayers.Geometry.Point(1, 1),
  312. new OpenLayers.Geometry.Point(0, 1),
  313. new OpenLayers.Geometry.Point(-1, 1),
  314. new OpenLayers.Geometry.Point(-1, 0),
  315. new OpenLayers.Geometry.Point(-1, -1)
  316. ]), null,
  317. typeof this.renderIntent == "string" ? null : this.renderIntent
  318. );
  319. // Override for box move - make sure that the center gets updated
  320. this.box.geometry.move = function(x, y) {
  321. control._moving = true;
  322. OpenLayers.Geometry.LineString.prototype.move.apply(this, arguments);
  323. control.center.move(x, y);
  324. delete control._moving;
  325. };
  326. // Overrides for vertex move, resize and rotate - make sure that
  327. // handle and rotationHandle geometries are also moved, resized and
  328. // rotated.
  329. var vertexMoveFn = function(x, y) {
  330. OpenLayers.Geometry.Point.prototype.move.apply(this, arguments);
  331. this._rotationHandle && this._rotationHandle.geometry.move(x, y);
  332. this._handle.geometry.move(x, y);
  333. };
  334. var vertexResizeFn = function(scale, center, ratio) {
  335. OpenLayers.Geometry.Point.prototype.resize.apply(this, arguments);
  336. this._rotationHandle && this._rotationHandle.geometry.resize(
  337. scale, center, ratio);
  338. this._handle.geometry.resize(scale, center, ratio);
  339. };
  340. var vertexRotateFn = function(angle, center) {
  341. OpenLayers.Geometry.Point.prototype.rotate.apply(this, arguments);
  342. this._rotationHandle && this._rotationHandle.geometry.rotate(
  343. angle, center);
  344. this._handle.geometry.rotate(angle, center);
  345. };
  346. // Override for handle move - make sure that the box and other handles
  347. // are updated, and finally transform the feature.
  348. var handleMoveFn = function(x, y) {
  349. var oldX = this.x, oldY = this.y;
  350. OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
  351. if(control._moving) {
  352. return;
  353. }
  354. var evt = control.dragControl.handlers.drag.evt;
  355. var preserveAspectRatio = !control._setfeature &&
  356. control.preserveAspectRatio;
  357. var reshape = !preserveAspectRatio && !(evt && evt.shiftKey);
  358. var oldGeom = new OpenLayers.Geometry.Point(oldX, oldY);
  359. var centerGeometry = control.center;
  360. this.rotate(-control.rotation, centerGeometry);
  361. oldGeom.rotate(-control.rotation, centerGeometry);
  362. var dx1 = this.x - centerGeometry.x;
  363. var dy1 = this.y - centerGeometry.y;
  364. var dx0 = dx1 - (this.x - oldGeom.x);
  365. var dy0 = dy1 - (this.y - oldGeom.y);
  366. if (control.irregular && !control._setfeature) {
  367. dx1 -= (this.x - oldGeom.x) / 2;
  368. dy1 -= (this.y - oldGeom.y) / 2;
  369. }
  370. this.x = oldX;
  371. this.y = oldY;
  372. var scale, ratio = 1;
  373. if (reshape) {
  374. scale = Math.abs(dy0) < 0.00001 ? 1 : dy1 / dy0;
  375. ratio = (Math.abs(dx0) < 0.00001 ? 1 : (dx1 / dx0)) / scale;
  376. } else {
  377. var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
  378. var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
  379. scale = l1 / l0;
  380. }
  381. // rotate the box to 0 before resizing - saves us some
  382. // calculations and is inexpensive because we don't drawFeature.
  383. control._moving = true;
  384. control.box.geometry.rotate(-control.rotation, centerGeometry);
  385. delete control._moving;
  386. control.box.geometry.resize(scale, centerGeometry, ratio);
  387. control.box.geometry.rotate(control.rotation, centerGeometry);
  388. control.transformFeature({scale: scale, ratio: ratio});
  389. if (control.irregular && !control._setfeature) {
  390. var newCenter = centerGeometry.clone();
  391. newCenter.x += Math.abs(oldX - centerGeometry.x) < 0.00001 ? 0 : (this.x - oldX);
  392. newCenter.y += Math.abs(oldY - centerGeometry.y) < 0.00001 ? 0 : (this.y - oldY);
  393. control.box.geometry.move(this.x - oldX, this.y - oldY);
  394. control.transformFeature({center: newCenter});
  395. }
  396. };
  397. // Override for rotation handle move - make sure that the box and
  398. // other handles are updated, and finally transform the feature.
  399. var rotationHandleMoveFn = function(x, y){
  400. var oldX = this.x, oldY = this.y;
  401. OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
  402. if(control._moving) {
  403. return;
  404. }
  405. var evt = control.dragControl.handlers.drag.evt;
  406. var constrain = (evt && evt.shiftKey) ? 45 : 1;
  407. var centerGeometry = control.center;
  408. var dx1 = this.x - centerGeometry.x;
  409. var dy1 = this.y - centerGeometry.y;
  410. var dx0 = dx1 - x;
  411. var dy0 = dy1 - y;
  412. this.x = oldX;
  413. this.y = oldY;
  414. var a0 = Math.atan2(dy0, dx0);
  415. var a1 = Math.atan2(dy1, dx1);
  416. var angle = a1 - a0;
  417. angle *= 180 / Math.PI;
  418. control._angle = (control._angle + angle) % 360;
  419. var diff = control.rotation % constrain;
  420. if(Math.abs(control._angle) >= constrain || diff !== 0) {
  421. angle = Math.round(control._angle / constrain) * constrain -
  422. diff;
  423. control._angle = 0;
  424. control.box.geometry.rotate(angle, centerGeometry);
  425. control.transformFeature({rotation: angle});
  426. }
  427. };
  428. var handles = new Array(8);
  429. var rotationHandles = new Array(4);
  430. var geom, handle, rotationHandle;
  431. var positions = ["sw", "s", "se", "e", "ne", "n", "nw", "w"];
  432. for(var i=0; i<8; ++i) {
  433. geom = this.box.geometry.components[i];
  434. handle = new OpenLayers.Feature.Vector(geom.clone(), {
  435. role: positions[i] + "-resize"
  436. }, typeof this.renderIntent == "string" ? null :
  437. this.renderIntent);
  438. if(i % 2 == 0) {
  439. rotationHandle = new OpenLayers.Feature.Vector(geom.clone(), {
  440. role: positions[i] + "-rotate"
  441. }, typeof this.rotationHandleSymbolizer == "string" ?
  442. null : this.rotationHandleSymbolizer);
  443. rotationHandle.geometry.move = rotationHandleMoveFn;
  444. geom._rotationHandle = rotationHandle;
  445. rotationHandles[i/2] = rotationHandle;
  446. }
  447. geom.move = vertexMoveFn;
  448. geom.resize = vertexResizeFn;
  449. geom.rotate = vertexRotateFn;
  450. handle.geometry.move = handleMoveFn;
  451. geom._handle = handle;
  452. handles[i] = handle;
  453. }
  454. this.rotationHandles = rotationHandles;
  455. this.handles = handles;
  456. },
  457. /**
  458. * Method: createControl
  459. * Creates a DragFeature control for this control.
  460. */
  461. createControl: function() {
  462. var control = this;
  463. this.dragControl = new OpenLayers.Control.DragFeature(this.layer, {
  464. documentDrag: true,
  465. // avoid moving the feature itself - move the box instead
  466. moveFeature: function(pixel) {
  467. if(this.feature === control.feature) {
  468. this.feature = control.box;
  469. }
  470. OpenLayers.Control.DragFeature.prototype.moveFeature.apply(this,
  471. arguments);
  472. },
  473. // transform while dragging
  474. onDrag: function(feature, pixel) {
  475. if(feature === control.box) {
  476. control.transformFeature({center: control.center});
  477. }
  478. },
  479. // set a new feature
  480. onStart: function(feature, pixel) {
  481. var eligible = !control.geometryTypes ||
  482. OpenLayers.Util.indexOf(control.geometryTypes,
  483. feature.geometry.CLASS_NAME) !== -1;
  484. var i = OpenLayers.Util.indexOf(control.handles, feature);
  485. i += OpenLayers.Util.indexOf(control.rotationHandles,
  486. feature);
  487. if(feature !== control.feature && feature !== control.box &&
  488. i == -2 && eligible) {
  489. control.setFeature(feature);
  490. }
  491. },
  492. onComplete: function(feature, pixel) {
  493. control.events.triggerEvent("transformcomplete",
  494. {feature: control.feature});
  495. }
  496. });
  497. },
  498. /**
  499. * Method: drawHandles
  500. * Draws the handles to match the box.
  501. */
  502. drawHandles: function() {
  503. var layer = this.layer;
  504. for(var i=0; i<8; ++i) {
  505. if(this.rotate && i % 2 === 0) {
  506. layer.drawFeature(this.rotationHandles[i/2],
  507. this.rotationHandleSymbolizer);
  508. }
  509. layer.drawFeature(this.handles[i], this.renderIntent);
  510. }
  511. },
  512. /**
  513. * Method: transformFeature
  514. * Transforms the feature.
  515. *
  516. * Parameters:
  517. * mods - {Object} An object with optional scale, ratio, rotation and
  518. * center properties.
  519. */
  520. transformFeature: function(mods) {
  521. if(!this._setfeature) {
  522. this.scale *= (mods.scale || 1);
  523. this.ratio *= (mods.ratio || 1);
  524. var oldRotation = this.rotation;
  525. this.rotation = (this.rotation + (mods.rotation || 0)) % 360;
  526. if(this.events.triggerEvent("beforetransform", mods) !== false) {
  527. var feature = this.feature;
  528. var geom = feature.geometry;
  529. var center = this.center;
  530. geom.rotate(-oldRotation, center);
  531. if(mods.scale || mods.ratio) {
  532. geom.resize(mods.scale, center, mods.ratio);
  533. } else if(mods.center) {
  534. feature.move(mods.center.getBounds().getCenterLonLat());
  535. }
  536. geom.rotate(this.rotation, center);
  537. this.layer.drawFeature(feature);
  538. feature.toState(OpenLayers.State.UPDATE);
  539. this.events.triggerEvent("transform", mods);
  540. }
  541. }
  542. this.layer.drawFeature(this.box, this.renderIntent);
  543. this.drawHandles();
  544. },
  545. /**
  546. * APIMethod: destroy
  547. * Take care of things that are not handled in superclass.
  548. */
  549. destroy: function() {
  550. var geom;
  551. for(var i=0; i<8; ++i) {
  552. geom = this.box.geometry.components[i];
  553. geom._handle.destroy();
  554. geom._handle = null;
  555. geom._rotationHandle && geom._rotationHandle.destroy();
  556. geom._rotationHandle = null;
  557. }
  558. this.center = null;
  559. this.feature = null;
  560. this.handles = null;
  561. this.rotationHandleSymbolizer = null;
  562. this.rotationHandles = null;
  563. this.box.destroy();
  564. this.box = null;
  565. this.layer = null;
  566. this.dragControl.destroy();
  567. this.dragControl = null;
  568. OpenLayers.Control.prototype.destroy.apply(this, arguments);
  569. },
  570. CLASS_NAME: "OpenLayers.Control.TransformFeature"
  571. });