Path.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
  2. * full list of contributors). Published under the Clear BSD license.
  3. * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  4. * full text of the license. */
  5. /**
  6. * @requires OpenLayers/Handler/Point.js
  7. * @requires OpenLayers/Geometry/Point.js
  8. * @requires OpenLayers/Geometry/LineString.js
  9. */
  10. /**
  11. * Class: OpenLayers.Handler.Path
  12. * Handler to draw a path on the map. Path is displayed on mouse down,
  13. * moves on mouse move, and is finished on mouse up.
  14. *
  15. * Inherits from:
  16. * - <OpenLayers.Handler.Point>
  17. */
  18. OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
  19. /**
  20. * Property: line
  21. * {<OpenLayers.Feature.Vector>}
  22. */
  23. line: null,
  24. /**
  25. * APIProperty: maxVertices
  26. * {Number} The maximum number of vertices which can be drawn by this
  27. * handler. When the number of vertices reaches maxVertices, the
  28. * geometry is automatically finalized. This property doesn't
  29. * apply if freehand is set. Default is null.
  30. */
  31. maxVertices: null,
  32. /**
  33. * Property: doubleTouchTolerance
  34. * {Number} Maximum number of pixels between two touches for
  35. * the gesture to be considered a "finalize feature" action.
  36. * Default is 20.
  37. */
  38. doubleTouchTolerance: 20,
  39. /**
  40. * Property: freehand
  41. * {Boolean} In freehand mode, the handler starts the path on mouse down,
  42. * adds a point for every mouse move, and finishes the path on mouse up.
  43. * Outside of freehand mode, a point is added to the path on every mouse
  44. * click and double-click finishes the path.
  45. */
  46. freehand: false,
  47. /**
  48. * Property: freehandToggle
  49. * {String} If set, freehandToggle is checked on mouse events and will set
  50. * the freehand mode to the opposite of this.freehand. To disallow
  51. * toggling between freehand and non-freehand mode, set freehandToggle to
  52. * null. Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
  53. */
  54. freehandToggle: 'shiftKey',
  55. /**
  56. * Property: timerId
  57. * {Integer} The timer used to test the double touch.
  58. */
  59. timerId: null,
  60. /**
  61. * Property: redoStack
  62. * {Array} Stack containing points removed with <undo>.
  63. */
  64. redoStack: null,
  65. /**
  66. * Constructor: OpenLayers.Handler.Path
  67. * Create a new path hander
  68. *
  69. * Parameters:
  70. * control - {<OpenLayers.Control>} The control that owns this handler
  71. * callbacks - {Object} An object with a properties whose values are
  72. * functions. Various callbacks described below.
  73. * options - {Object} An optional object with properties to be set on the
  74. * handler
  75. *
  76. * Named callbacks:
  77. * create - Called when a sketch is first created. Callback called with
  78. * the creation point geometry and sketch feature.
  79. * modify - Called with each move of a vertex with the vertex (point)
  80. * geometry and the sketch feature.
  81. * point - Called as each point is added. Receives the new point geometry.
  82. * done - Called when the point drawing is finished. The callback will
  83. * recieve a single argument, the linestring geometry.
  84. * cancel - Called when the handler is deactivated while drawing. The
  85. * cancel callback will receive a geometry.
  86. */
  87. initialize: function(control, callbacks, options) {
  88. OpenLayers.Handler.Point.prototype.initialize.apply(this, arguments);
  89. },
  90. /**
  91. * Method: createFeature
  92. * Add temporary geometries
  93. *
  94. * Parameters:
  95. * pixel - {<OpenLayers.Pixel>} The initial pixel location for the new
  96. * feature.
  97. */
  98. createFeature: function(pixel) {
  99. var lonlat = this.map.getLonLatFromPixel(pixel);
  100. var geometry = new OpenLayers.Geometry.Point(
  101. lonlat.lon, lonlat.lat
  102. );
  103. this.point = new OpenLayers.Feature.Vector(geometry);
  104. this.line = new OpenLayers.Feature.Vector(
  105. new OpenLayers.Geometry.LineString([this.point.geometry])
  106. );
  107. this.callback("create", [this.point.geometry, this.getSketch()]);
  108. this.point.geometry.clearBounds();
  109. this.layer.addFeatures([this.line, this.point], {silent: true});
  110. },
  111. /**
  112. * Method: destroyFeature
  113. * Destroy temporary geometries
  114. *
  115. * Parameters:
  116. * force - {Boolean} Destroy even if persist is true.
  117. */
  118. destroyFeature: function(force) {
  119. OpenLayers.Handler.Point.prototype.destroyFeature.call(
  120. this, force);
  121. this.line = null;
  122. },
  123. /**
  124. * Method: destroyPersistedFeature
  125. * Destroy the persisted feature.
  126. */
  127. destroyPersistedFeature: function() {
  128. var layer = this.layer;
  129. if(layer && layer.features.length > 2) {
  130. this.layer.features[0].destroy();
  131. }
  132. },
  133. /**
  134. * Method: removePoint
  135. * Destroy the temporary point.
  136. */
  137. removePoint: function() {
  138. if(this.point) {
  139. this.layer.removeFeatures([this.point]);
  140. }
  141. },
  142. /**
  143. * Method: addPoint
  144. * Add point to geometry. Send the point index to override
  145. * the behavior of LinearRing that disregards adding duplicate points.
  146. *
  147. * Parameters:
  148. * pixel - {<OpenLayers.Pixel>} The pixel location for the new point.
  149. */
  150. addPoint: function(pixel) {
  151. this.layer.removeFeatures([this.point]);
  152. var lonlat = this.control.map.getLonLatFromPixel(pixel);
  153. this.point = new OpenLayers.Feature.Vector(
  154. new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat)
  155. );
  156. this.line.geometry.addComponent(
  157. this.point.geometry, this.line.geometry.components.length
  158. );
  159. this.layer.addFeatures([this.point]);
  160. this.callback("point", [this.point.geometry, this.getGeometry()]);
  161. this.callback("modify", [this.point.geometry, this.getSketch()]);
  162. this.drawFeature();
  163. delete this.redoStack;
  164. },
  165. /**
  166. * Method: insertXY
  167. * Insert a point in the current sketch given x & y coordinates. The new
  168. * point is inserted immediately before the most recently drawn point.
  169. *
  170. * Parameters:
  171. * x - {Number} The x-coordinate of the point.
  172. * y - {Number} The y-coordinate of the point.
  173. */
  174. insertXY: function(x, y) {
  175. this.line.geometry.addComponent(
  176. new OpenLayers.Geometry.Point(x, y),
  177. this.getCurrentPointIndex()
  178. );
  179. this.drawFeature();
  180. delete this.redoStack;
  181. },
  182. /**
  183. * Method: insertDeltaXY
  184. * Insert a point given offsets from the previously inserted point.
  185. *
  186. * Parameters:
  187. * dx - {Number} The x-coordinate offset of the point.
  188. * dy - {Number} The y-coordinate offset of the point.
  189. */
  190. insertDeltaXY: function(dx, dy) {
  191. var previousIndex = this.getCurrentPointIndex() - 1;
  192. var p0 = this.line.geometry.components[previousIndex];
  193. if (p0 && !isNaN(p0.x) && !isNaN(p0.y)) {
  194. this.insertXY(p0.x + dx, p0.y + dy);
  195. }
  196. },
  197. /**
  198. * Method: insertDirectionLength
  199. * Insert a point in the current sketch given a direction and a length.
  200. *
  201. * Parameters:
  202. * direction - {Number} Degrees clockwise from the positive x-axis.
  203. * length - {Number} Distance from the previously drawn point.
  204. */
  205. insertDirectionLength: function(direction, length) {
  206. direction *= Math.PI / 180;
  207. var dx = length * Math.cos(direction);
  208. var dy = length * Math.sin(direction);
  209. this.insertDeltaXY(dx, dy);
  210. },
  211. /**
  212. * Method: insertDeflectionLength
  213. * Insert a point in the current sketch given a deflection and a length.
  214. * The deflection should be degrees clockwise from the previously
  215. * digitized segment.
  216. *
  217. * Parameters:
  218. * deflection - {Number} Degrees clockwise from the previous segment.
  219. * length - {Number} Distance from the previously drawn point.
  220. */
  221. insertDeflectionLength: function(deflection, length) {
  222. var previousIndex = this.getCurrentPointIndex() - 1;
  223. if (previousIndex > 0) {
  224. var p1 = this.line.geometry.components[previousIndex];
  225. var p0 = this.line.geometry.components[previousIndex-1];
  226. var theta = Math.atan2(p1.y - p0.y, p1.x - p0.x);
  227. this.insertDirectionLength(
  228. (theta * 180 / Math.PI) + deflection, length
  229. );
  230. }
  231. },
  232. /**
  233. * Method: getCurrentPointIndex
  234. *
  235. * Returns:
  236. * {Number} The index of the most recently drawn point.
  237. */
  238. getCurrentPointIndex: function() {
  239. return this.line.geometry.components.length - 1;
  240. },
  241. /**
  242. * Method: undo
  243. * Remove the most recently added point in the sketch geometry.
  244. *
  245. * Returns:
  246. * {Boolean} A point was removed.
  247. */
  248. undo: function() {
  249. var geometry = this.line.geometry;
  250. var components = geometry.components;
  251. var index = this.getCurrentPointIndex() - 1;
  252. var target = components[index];
  253. var undone = geometry.removeComponent(target);
  254. if (undone) {
  255. if (!this.redoStack) {
  256. this.redoStack = [];
  257. }
  258. this.redoStack.push(target);
  259. this.drawFeature();
  260. }
  261. return undone;
  262. },
  263. /**
  264. * Method: redo
  265. * Reinsert the most recently removed point resulting from an <undo> call.
  266. * The undo stack is deleted whenever a point is added by other means.
  267. *
  268. * Returns:
  269. * {Boolean} A point was added.
  270. */
  271. redo: function() {
  272. var target = this.redoStack && this.redoStack.pop();
  273. if (target) {
  274. this.line.geometry.addComponent(target, this.getCurrentPointIndex());
  275. this.drawFeature();
  276. }
  277. return !!target;
  278. },
  279. /**
  280. * Method: freehandMode
  281. * Determine whether to behave in freehand mode or not.
  282. *
  283. * Returns:
  284. * {Boolean}
  285. */
  286. freehandMode: function(evt) {
  287. return (this.freehandToggle && evt[this.freehandToggle]) ?
  288. !this.freehand : this.freehand;
  289. },
  290. /**
  291. * Method: modifyFeature
  292. * Modify the existing geometry given the new point
  293. *
  294. * Parameters:
  295. * pixel - {<OpenLayers.Pixel>} The updated pixel location for the latest
  296. * point.
  297. * drawing - {Boolean} Indicate if we're currently drawing.
  298. */
  299. modifyFeature: function(pixel, drawing) {
  300. if(!this.line) {
  301. this.createFeature(pixel);
  302. }
  303. var lonlat = this.control.map.getLonLatFromPixel(pixel);
  304. this.point.geometry.x = lonlat.lon;
  305. this.point.geometry.y = lonlat.lat;
  306. this.callback("modify", [this.point.geometry, this.getSketch(), drawing]);
  307. this.point.geometry.clearBounds();
  308. this.drawFeature();
  309. },
  310. /**
  311. * Method: drawFeature
  312. * Render geometries on the temporary layer.
  313. */
  314. drawFeature: function() {
  315. this.layer.drawFeature(this.line, this.style);
  316. this.layer.drawFeature(this.point, this.style);
  317. },
  318. /**
  319. * Method: getSketch
  320. * Return the sketch feature.
  321. *
  322. * Returns:
  323. * {<OpenLayers.Feature.Vector>}
  324. */
  325. getSketch: function() {
  326. return this.line;
  327. },
  328. /**
  329. * Method: getGeometry
  330. * Return the sketch geometry. If <multi> is true, this will return
  331. * a multi-part geometry.
  332. *
  333. * Returns:
  334. * {<OpenLayers.Geometry.LineString>}
  335. */
  336. getGeometry: function() {
  337. var geometry = this.line && this.line.geometry;
  338. if(geometry && this.multi) {
  339. geometry = new OpenLayers.Geometry.MultiLineString([geometry]);
  340. }
  341. return geometry;
  342. },
  343. /**
  344. * method: touchstart
  345. * handle touchstart.
  346. *
  347. * parameters:
  348. * evt - {event} the browser event
  349. *
  350. * returns:
  351. * {boolean} allow event propagation
  352. */
  353. touchstart: function(evt) {
  354. if (this.timerId &&
  355. this.passesTolerance(this.lastTouchPx, evt.xy,
  356. this.doubleTouchTolerance)) {
  357. // double-tap, finalize the geometry
  358. this.finishGeometry();
  359. window.clearTimeout(this.timerId);
  360. this.timerId = null;
  361. return false;
  362. } else {
  363. if (this.timerId) {
  364. window.clearTimeout(this.timerId);
  365. this.timerId = null;
  366. }
  367. this.timerId = window.setTimeout(
  368. OpenLayers.Function.bind(function() {
  369. this.timerId = null;
  370. }, this), 300);
  371. return OpenLayers.Handler.Point.prototype.touchstart.call(this, evt);
  372. }
  373. },
  374. /**
  375. * Method: down
  376. * Handle mousedown and touchstart. Add a new point to the geometry and
  377. * render it. Return determines whether to propagate the event on the map.
  378. *
  379. * Parameters:
  380. * evt - {Event} The browser event
  381. *
  382. * Returns:
  383. * {Boolean} Allow event propagation
  384. */
  385. down: function(evt) {
  386. var stopDown = this.stopDown;
  387. if(this.freehandMode(evt)) {
  388. stopDown = true;
  389. }
  390. if (!this.touch && (!this.lastDown ||
  391. !this.passesTolerance(this.lastDown, evt.xy,
  392. this.pixelTolerance))) {
  393. this.modifyFeature(evt.xy, !!this.lastUp);
  394. }
  395. this.mouseDown = true;
  396. this.lastDown = evt.xy;
  397. this.stoppedDown = stopDown;
  398. return !stopDown;
  399. },
  400. /**
  401. * Method: move
  402. * Handle mousemove and touchmove. Adjust the geometry and redraw.
  403. * Return determines whether to propagate the event on the map.
  404. *
  405. * Parameters:
  406. * evt - {Event} The browser event
  407. *
  408. * Returns:
  409. * {Boolean} Allow event propagation
  410. */
  411. move: function (evt) {
  412. if(this.stoppedDown && this.freehandMode(evt)) {
  413. if(this.persist) {
  414. this.destroyPersistedFeature();
  415. }
  416. this.addPoint(evt.xy);
  417. return false;
  418. }
  419. if (!this.touch && (!this.mouseDown || this.stoppedDown)) {
  420. this.modifyFeature(evt.xy, !!this.lastUp);
  421. }
  422. return true;
  423. },
  424. /**
  425. * Method: up
  426. * Handle mouseup and touchend. Send the latest point in the geometry to
  427. * the control. Return determines whether to propagate the event on the map.
  428. *
  429. * Parameters:
  430. * evt - {Event} The browser event
  431. *
  432. * Returns:
  433. * {Boolean} Allow event propagation
  434. */
  435. up: function (evt) {
  436. if (this.mouseDown && (!this.lastUp || !this.lastUp.equals(evt.xy))) {
  437. if(this.stoppedDown && this.freehandMode(evt)) {
  438. if (this.persist) {
  439. this.destroyPersistedFeature();
  440. }
  441. this.removePoint();
  442. this.finalize();
  443. } else {
  444. if (this.passesTolerance(this.lastDown, evt.xy,
  445. this.pixelTolerance)) {
  446. if (this.touch) {
  447. this.modifyFeature(evt.xy);
  448. }
  449. if(this.lastUp == null && this.persist) {
  450. this.destroyPersistedFeature();
  451. }
  452. this.addPoint(evt.xy);
  453. this.lastUp = evt.xy;
  454. if(this.line.geometry.components.length === this.maxVertices + 1) {
  455. this.finishGeometry();
  456. }
  457. }
  458. }
  459. }
  460. this.stoppedDown = this.stopDown;
  461. this.mouseDown = false;
  462. return !this.stopUp;
  463. },
  464. /**
  465. * APIMethod: finishGeometry
  466. * Finish the geometry and send it back to the control.
  467. */
  468. finishGeometry: function() {
  469. var index = this.line.geometry.components.length - 1;
  470. this.line.geometry.removeComponent(this.line.geometry.components[index]);
  471. this.removePoint();
  472. this.finalize();
  473. },
  474. /**
  475. * Method: dblclick
  476. * Handle double-clicks.
  477. *
  478. * Parameters:
  479. * evt - {Event} The browser event
  480. *
  481. * Returns:
  482. * {Boolean} Allow event propagation
  483. */
  484. dblclick: function(evt) {
  485. if(!this.freehandMode(evt)) {
  486. this.finishGeometry();
  487. }
  488. return false;
  489. },
  490. CLASS_NAME: "OpenLayers.Handler.Path"
  491. });