Path.js 17 KB

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