Snapping.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  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/Control.js
  7. * @requires OpenLayers/Layer/Vector.js
  8. */
  9. /**
  10. * Class: OpenLayers.Control.Snapping
  11. * Acts as a snapping agent while editing vector features.
  12. *
  13. * Inherits from:
  14. * - <OpenLayers.Control>
  15. */
  16. OpenLayers.Control.Snapping = OpenLayers.Class(OpenLayers.Control, {
  17. /**
  18. * Constant: EVENT_TYPES
  19. * {Array(String)} Supported application event types. Register a listener
  20. * for a particular event with the following syntax:
  21. * (code)
  22. * control.events.register(type, obj, listener);
  23. * (end)
  24. *
  25. * Listeners will be called with a reference to an event object. The
  26. * properties of this event depends on exactly what happened.
  27. *
  28. * Supported control event types (in addition to those from <OpenLayers.Control>):
  29. * beforesnap - Triggered before a snap occurs. Listeners receive an
  30. * event object with *point*, *x*, *y*, *distance*, *layer*, and
  31. * *snapType* properties. The point property will be original point
  32. * geometry considered for snapping. The x and y properties represent
  33. * coordinates the point will receive. The distance is the distance
  34. * of the snap. The layer is the target layer. The snapType property
  35. * will be one of "node", "vertex", or "edge". Return false to stop
  36. * snapping from occurring.
  37. * snap - Triggered when a snap occurs. Listeners receive an event with
  38. * *point*, *snapType*, *layer*, and *distance* properties. The point
  39. * will be the location snapped to. The snapType will be one of "node",
  40. * "vertex", or "edge". The layer will be the target layer. The
  41. * distance will be the distance of the snap in map units.
  42. * unsnap - Triggered when a vertex is unsnapped. Listeners receive an
  43. * event with a *point* property.
  44. */
  45. EVENT_TYPES: ["beforesnap", "snap", "unsnap"],
  46. /**
  47. * CONSTANT: DEFAULTS
  48. * Default target properties.
  49. */
  50. DEFAULTS: {
  51. tolerance: 10,
  52. node: true,
  53. edge: true,
  54. vertex: true
  55. },
  56. /**
  57. * Property: greedy
  58. * {Boolean} Snap to closest feature in first layer with an eligible
  59. * feature. Default is true.
  60. */
  61. greedy: true,
  62. /**
  63. * Property: precedence
  64. * {Array} List representing precedence of different snapping types.
  65. * Default is "node", "vertex", "edge".
  66. */
  67. precedence: ["node", "vertex", "edge"],
  68. /**
  69. * Property: resolution
  70. * {Float} The map resolution for the previously considered snap.
  71. */
  72. resolution: null,
  73. /**
  74. * Property: geoToleranceCache
  75. * {Object} A cache of geo-tolerances. Tolerance values (in map units) are
  76. * calculated when the map resolution changes.
  77. */
  78. geoToleranceCache: null,
  79. /**
  80. * Property: layer
  81. * {<OpenLayers.Layer.Vector>} The current editable layer. Set at
  82. * construction or after construction with <setLayer>.
  83. */
  84. layer: null,
  85. /**
  86. * Property: feature
  87. * {<OpenLayers.Feature.Vector>} The current editable feature.
  88. */
  89. feature: null,
  90. /**
  91. * Property: point
  92. * {<OpenLayers.Geometry.Point>} The currently snapped vertex.
  93. */
  94. point: null,
  95. /**
  96. * Constructor: OpenLayers.Control.Snapping
  97. * Creates a new snapping control. A control is constructed with an editable
  98. * layer and a set of configuration objects for target layers. While the
  99. * control is active, dragging vertices while drawing new features or
  100. * modifying existing features on the editable layer will engage
  101. * snapping to features on the target layers. Whether a vertex snaps to
  102. * a feature on a target layer depends on the target layer configuration.
  103. *
  104. * Parameters:
  105. * options - {Object} An object containing all configuration properties for
  106. * the control.
  107. *
  108. * Valid options:
  109. * layer - {OpenLayers.Layer.Vector} The editable layer. Features from this
  110. * layer that are digitized or modified may have vertices snapped to
  111. * features from any of the target layers.
  112. * targets - {Array(Object | OpenLayers.Layer.Vector)} A list of objects for
  113. * configuring target layers. See valid properties of the target
  114. * objects below. If the items in the targets list are vector layers
  115. * (instead of configuration objects), the defaults from the <defaults>
  116. * property will apply. The editable layer itself may be a target
  117. * layer - allowing newly created or edited features to be snapped to
  118. * existing features from the same layer. If no targets are provided
  119. * the layer given in the constructor (as <layer>) will become the
  120. * initial target.
  121. * defaults - {Object} An object with default properties to be applied
  122. * to all target objects.
  123. * greedy - {Boolean} Snap to closest feature in first target layer that
  124. * applies. Default is true. If false, all features in all target
  125. * layers will be checked and the closest feature in all target layers
  126. * will be chosen. The greedy property determines if the order of the
  127. * target layers is significant. By default, the order of the target
  128. * layers is significant where layers earlier in the target layer list
  129. * have precedence over layers later in the list. Within a single
  130. * layer, the closest feature is always chosen for snapping. This
  131. * property only determines whether the search for a closer feature
  132. * continues after an eligible feature is found in a target layer.
  133. *
  134. * Valid target properties:
  135. * layer - {OpenLayers.Layer.Vector} A target layer. Features from this
  136. * layer will be eligible to act as snapping target for the editable
  137. * layer.
  138. * tolerance - {Float} The distance (in pixels) at which snapping may occur.
  139. * Default is 10.
  140. * node - {Boolean} Snap to nodes (first or last point in a geometry) in
  141. * target layer. Default is true.
  142. * nodeTolerance - {Float} Optional distance at which snapping may occur
  143. * for nodes specifically. If none is provided, <tolerance> will be
  144. * used.
  145. * vertex - {Boolean} Snap to vertices in target layer. Default is true.
  146. * vertexTolerance - {Float} Optional distance at which snapping may occur
  147. * for vertices specifically. If none is provided, <tolerance> will be
  148. * used.
  149. * edge - {Boolean} Snap to edges in target layer. Default is true.
  150. * edgeTolerance - {Float} Optional distance at which snapping may occur
  151. * for edges specifically. If none is provided, <tolerance> will be
  152. * used.
  153. * filter - {OpenLayers.Filter} Optional filter to evaluate to determine if
  154. * feature is eligible for snapping. If filter evaluates to true for a
  155. * target feature a vertex may be snapped to the feature.
  156. * minResolution - {Number} If a minResolution is provided, snapping to this
  157. * target will only be considered if the map resolution is greater than
  158. * or equal to this value (the minResolution is inclusive). Default is
  159. * no minimum resolution limit.
  160. * maxResolution - {Number} If a maxResolution is provided, snapping to this
  161. * target will only be considered if the map resolution is strictly
  162. * less than this value (the maxResolution is exclusive). Default is
  163. * no maximum resolution limit.
  164. */
  165. initialize: function(options) {
  166. // concatenate events specific to measure with those from the base
  167. Array.prototype.push.apply(
  168. this.EVENT_TYPES, OpenLayers.Control.prototype.EVENT_TYPES
  169. );
  170. OpenLayers.Control.prototype.initialize.apply(this, [options]);
  171. this.options = options || {}; // TODO: this could be done by the super
  172. // set the editable layer if provided
  173. if(this.options.layer) {
  174. this.setLayer(this.options.layer);
  175. }
  176. // configure target layers
  177. var defaults = OpenLayers.Util.extend({}, this.options.defaults);
  178. this.defaults = OpenLayers.Util.applyDefaults(defaults, this.DEFAULTS);
  179. this.setTargets(this.options.targets);
  180. if(this.targets.length === 0 && this.layer) {
  181. this.addTargetLayer(this.layer);
  182. }
  183. this.geoToleranceCache = {};
  184. },
  185. /**
  186. * APIMethod: setLayer
  187. * Set the editable layer. Call the setLayer method if the editable layer
  188. * changes and the same control should be used on a new editable layer.
  189. * If the control is already active, it will be active after the new
  190. * layer is set.
  191. *
  192. * Parameters:
  193. * layer - {OpenLayers.Layer.Vector} The new editable layer.
  194. */
  195. setLayer: function(layer) {
  196. if(this.active) {
  197. this.deactivate();
  198. this.layer = layer;
  199. this.activate();
  200. } else {
  201. this.layer = layer;
  202. }
  203. },
  204. /**
  205. * Method: setTargets
  206. * Set the targets for the snapping agent.
  207. *
  208. * Parameters:
  209. * targets - {Array} An array of target configs or target layers.
  210. */
  211. setTargets: function(targets) {
  212. this.targets = [];
  213. if(targets && targets.length) {
  214. var target;
  215. for(var i=0, len=targets.length; i<len; ++i) {
  216. target = targets[i];
  217. if(target instanceof OpenLayers.Layer.Vector) {
  218. this.addTargetLayer(target);
  219. } else {
  220. this.addTarget(target);
  221. }
  222. }
  223. }
  224. },
  225. /**
  226. * Method: addTargetLayer
  227. * Add a target layer with the default target config.
  228. *
  229. * Parameters:
  230. * layer - {<OpenLayers.Layer.Vector>} A target layer.
  231. */
  232. addTargetLayer: function(layer) {
  233. this.addTarget({layer: layer});
  234. },
  235. /**
  236. * Method: addTarget
  237. * Add a configured target layer.
  238. *
  239. * Parameters:
  240. * target - {Object} A target config.
  241. */
  242. addTarget: function(target) {
  243. target = OpenLayers.Util.applyDefaults(target, this.defaults);
  244. target.nodeTolerance = target.nodeTolerance || target.tolerance;
  245. target.vertexTolerance = target.vertexTolerance || target.tolerance;
  246. target.edgeTolerance = target.edgeTolerance || target.tolerance;
  247. this.targets.push(target);
  248. },
  249. /**
  250. * Method: removeTargetLayer
  251. * Remove a target layer.
  252. *
  253. * Parameters:
  254. * layer - {<OpenLayers.Layer.Vector>} The target layer to remove.
  255. */
  256. removeTargetLayer: function(layer) {
  257. var target;
  258. for(var i=this.targets.length-1; i>=0; --i) {
  259. target = this.targets[i];
  260. if(target.layer === layer) {
  261. this.removeTarget(target);
  262. }
  263. }
  264. },
  265. /**
  266. * Method: removeTarget
  267. * Remove a target.
  268. *
  269. * Parameters:
  270. * target - {Object} A target config.
  271. *
  272. * Returns:
  273. * {Array} The targets array.
  274. */
  275. removeTarget: function(target) {
  276. return OpenLayers.Util.removeItem(this.targets, target);
  277. },
  278. /**
  279. * APIMethod: activate
  280. * Activate the control. Activating the control registers listeners for
  281. * editing related events so that during feature creation and
  282. * modification, moving vertices will trigger snapping.
  283. */
  284. activate: function() {
  285. var activated = OpenLayers.Control.prototype.activate.call(this);
  286. if(activated) {
  287. if(this.layer && this.layer.events) {
  288. this.layer.events.on({
  289. sketchstarted: this.onSketchModified,
  290. sketchmodified: this.onSketchModified,
  291. vertexmodified: this.onVertexModified,
  292. scope: this
  293. });
  294. }
  295. }
  296. return activated;
  297. },
  298. /**
  299. * APIMethod: deactivate
  300. * Deactivate the control. Deactivating the control unregisters listeners
  301. * so feature editing may proceed without engaging the snapping agent.
  302. */
  303. deactivate: function() {
  304. var deactivated = OpenLayers.Control.prototype.deactivate.call(this);
  305. if(deactivated) {
  306. if(this.layer && this.layer.events) {
  307. this.layer.events.un({
  308. sketchstarted: this.onSketchModified,
  309. sketchmodified: this.onSketchModified,
  310. vertexmodified: this.onVertexModified,
  311. scope: this
  312. });
  313. }
  314. }
  315. this.feature = null;
  316. this.point = null;
  317. return deactivated;
  318. },
  319. /**
  320. * Method: onSketchModified
  321. * Registered as a listener for the sketchmodified event on the editable
  322. * layer.
  323. *
  324. * Parameters:
  325. * event - {Object} The sketch modified event.
  326. */
  327. onSketchModified: function(event) {
  328. this.feature = event.feature;
  329. this.considerSnapping(event.vertex, event.vertex);
  330. },
  331. /**
  332. * Method: onVertexModified
  333. * Registered as a listener for the vertexmodified event on the editable
  334. * layer.
  335. *
  336. * Parameters:
  337. * event - {Object} The vertex modified event.
  338. */
  339. onVertexModified: function(event) {
  340. this.feature = event.feature;
  341. var loc = this.layer.map.getLonLatFromViewPortPx(event.pixel);
  342. this.considerSnapping(
  343. event.vertex, new OpenLayers.Geometry.Point(loc.lon, loc.lat)
  344. );
  345. },
  346. /**
  347. * Method: considerSnapping
  348. *
  349. * Parameters:
  350. * point - {<OpenLayers.Geometry.Point>} The vertex to be snapped (or
  351. * unsnapped).
  352. * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
  353. * coords.
  354. */
  355. considerSnapping: function(point, loc) {
  356. var best = {
  357. rank: Number.POSITIVE_INFINITY,
  358. dist: Number.POSITIVE_INFINITY,
  359. x: null, y: null
  360. };
  361. var snapped = false;
  362. var result, target;
  363. for(var i=0, len=this.targets.length; i<len; ++i) {
  364. target = this.targets[i];
  365. result = this.testTarget(target, loc);
  366. if(result) {
  367. if(this.greedy) {
  368. best = result;
  369. best.target = target;
  370. snapped = true;
  371. break;
  372. } else {
  373. if((result.rank < best.rank) ||
  374. (result.rank === best.rank && result.dist < best.dist)) {
  375. best = result;
  376. best.target = target;
  377. snapped = true;
  378. }
  379. }
  380. }
  381. }
  382. if(snapped) {
  383. var proceed = this.events.triggerEvent("beforesnap", {
  384. point: point, x: best.x, y: best.y, distance: best.dist,
  385. layer: best.target.layer, snapType: this.precedence[best.rank]
  386. });
  387. if(proceed !== false) {
  388. point.x = best.x;
  389. point.y = best.y;
  390. this.point = point;
  391. this.events.triggerEvent("snap", {
  392. point: point,
  393. snapType: this.precedence[best.rank],
  394. layer: best.target.layer,
  395. distance: best.dist
  396. });
  397. } else {
  398. snapped = false;
  399. }
  400. }
  401. if(this.point && !snapped) {
  402. point.x = loc.x;
  403. point.y = loc.y;
  404. this.point = null;
  405. this.events.triggerEvent("unsnap", {point: point});
  406. }
  407. },
  408. /**
  409. * Method: testTarget
  410. *
  411. * Parameters:
  412. * target - {Object} Object with target layer configuration.
  413. * loc - {<OpenLayers.Geometry.Point>} The location of the mouse in map
  414. * coords.
  415. *
  416. * Returns:
  417. * {Object} A result object with rank, dist, x, and y properties.
  418. * Returns null if candidate is not eligible for snapping.
  419. */
  420. testTarget: function(target, loc) {
  421. var resolution = this.layer.map.getResolution();
  422. if ("minResolution" in target) {
  423. if (resolution < target.minResolution) {
  424. return null;
  425. }
  426. }
  427. if ("maxResolution" in target) {
  428. if (resolution >= target.maxResolution) {
  429. return null;
  430. }
  431. }
  432. var tolerance = {
  433. node: this.getGeoTolerance(target.nodeTolerance, resolution),
  434. vertex: this.getGeoTolerance(target.vertexTolerance, resolution),
  435. edge: this.getGeoTolerance(target.edgeTolerance, resolution)
  436. };
  437. // this could be cached if we don't support setting tolerance values directly
  438. var maxTolerance = Math.max(
  439. tolerance.node, tolerance.vertex, tolerance.edge
  440. );
  441. var result = {
  442. rank: Number.POSITIVE_INFINITY, dist: Number.POSITIVE_INFINITY
  443. };
  444. var eligible = false;
  445. var features = target.layer.features;
  446. var feature, type, vertices, vertex, closest, dist, found;
  447. var numTypes = this.precedence.length;
  448. var ll = new OpenLayers.LonLat(loc.x, loc.y);
  449. for(var i=0, len=features.length; i<len; ++i) {
  450. feature = features[i];
  451. if(feature !== this.feature && !feature._sketch &&
  452. feature.state !== OpenLayers.State.DELETE &&
  453. (!target.filter || target.filter.evaluate(feature.attributes))) {
  454. if(feature.atPoint(ll, maxTolerance, maxTolerance)) {
  455. for(var j=0, stop=Math.min(result.rank+1, numTypes); j<stop; ++j) {
  456. type = this.precedence[j];
  457. if(target[type]) {
  458. if(type === "edge") {
  459. closest = feature.geometry.distanceTo(loc, {details: true});
  460. dist = closest.distance;
  461. if(dist <= tolerance[type] && dist < result.dist) {
  462. result = {
  463. rank: j, dist: dist,
  464. x: closest.x0, y: closest.y0 // closest coords on feature
  465. };
  466. eligible = true;
  467. // don't look for lower precedence types for this feature
  468. break;
  469. }
  470. } else {
  471. // look for nodes or vertices
  472. vertices = feature.geometry.getVertices(type === "node");
  473. found = false;
  474. for(var k=0, klen=vertices.length; k<klen; ++k) {
  475. vertex = vertices[k];
  476. dist = vertex.distanceTo(loc);
  477. if(dist <= tolerance[type] &&
  478. (j < result.rank || (j === result.rank && dist < result.dist))) {
  479. result = {
  480. rank: j, dist: dist,
  481. x: vertex.x, y: vertex.y
  482. };
  483. eligible = true;
  484. found = true;
  485. }
  486. }
  487. if(found) {
  488. // don't look for lower precedence types for this feature
  489. break;
  490. }
  491. }
  492. }
  493. }
  494. }
  495. }
  496. }
  497. return eligible ? result : null;
  498. },
  499. /**
  500. * Method: getGeoTolerance
  501. * Calculate a tolerance in map units given a tolerance in pixels. This
  502. * takes advantage of the <geoToleranceCache> when the map resolution
  503. * has not changed.
  504. *
  505. * Parameters:
  506. * tolerance - {Number} A tolerance value in pixels.
  507. * resolution - {Number} Map resolution.
  508. *
  509. * Returns:
  510. * {Number} A tolerance value in map units.
  511. */
  512. getGeoTolerance: function(tolerance, resolution) {
  513. if(resolution !== this.resolution) {
  514. this.resolution = resolution;
  515. this.geoToleranceCache = {};
  516. }
  517. var geoTolerance = this.geoToleranceCache[tolerance];
  518. if(geoTolerance === undefined) {
  519. geoTolerance = tolerance * resolution;
  520. this.geoToleranceCache[tolerance] = geoTolerance;
  521. }
  522. return geoTolerance;
  523. },
  524. /**
  525. * Method: destroy
  526. * Clean up the control.
  527. */
  528. destroy: function() {
  529. if(this.active) {
  530. this.deactivate(); // TODO: this should be handled by the super
  531. }
  532. delete this.layer;
  533. delete this.targets;
  534. OpenLayers.Control.prototype.destroy.call(this);
  535. },
  536. CLASS_NAME: "OpenLayers.Control.Snapping"
  537. });