{"action":"created","comment":{"url":"https://api.github.com/repos/openlayers/ol3/pulls/comments/27282278","id":27282278,"diff_hunk":"@@ -0,0 +1,603 @@\n+goog.provide('ol.interaction.Snap');\n+goog.provide('ol.interaction.SnapProperty');\n+\n+goog.require('goog.array');\n+goog.require('goog.asserts');\n+goog.require('goog.events');\n+goog.require('goog.events.EventType');\n+goog.require('goog.object');\n+goog.require('ol.Collection');\n+goog.require('ol.CollectionEvent');\n+goog.require('ol.CollectionEventType');\n+goog.require('ol.Extent');\n+goog.require('ol.Feature');\n+goog.require('ol.Observable');\n+goog.require('ol.coordinate');\n+goog.require('ol.extent');\n+goog.require('ol.geom.Geometry');\n+goog.require('ol.interaction.Pointer');\n+goog.require('ol.source.Vector');\n+goog.require('ol.source.VectorEvent');\n+goog.require('ol.source.VectorEventType');\n+goog.require('ol.structs.RBush');\n+\n+\n+\n+/**\n+ * @classdesc\n+ * Handles snapping of vector features while modifying or drawing them.  The\n+ * features can come from a {@link ol.source.Vector}, a {@link ol.Collection}\n+ * or a plain array. Any interaction object that allows the user to interact\n+ * with the features using the mouse can benefit from the snapping, as long\n+ * as it is added before.\n+ *\n+ * The snap interaction modifies map browser event `coordinate` and `pixel`\n+ * properties to force the snap to occur to any interaction that them.\n+ *\n+ * Example:\n+ *\n+ *     var snap = new ol.interaction.Snap({\n+ *       source: source\n+ *     });\n+ *\n+ * @constructor\n+ * @extends {ol.interaction.Pointer}\n+ * @param {olx.interaction.SnapOptions=} opt_options Options.\n+ * @api\n+ */\n+ol.interaction.Snap = function(opt_options) {\n+\n+  goog.base(this, {\n+    handleEvent: ol.interaction.Snap.handleEvent,\n+    handleDownEvent: goog.functions.TRUE,\n+    handleUpEvent: ol.interaction.Snap.handleUpEvent\n+  });\n+\n+  var options = goog.isDef(opt_options) ? opt_options : {};\n+\n+  /**\n+   * @type {?ol.source.Vector}\n+   * @private\n+   */\n+  this.source_ = goog.isDef(options.source) ? options.source : null;\n+\n+  /**\n+   * @type {?ol.Collection.<ol.Feature>}\n+   * @private\n+   */\n+  this.features_ = goog.isDef(options.features) ?\n+      goog.isArray(options.features) ?\n+          new ol.Collection(options.features) :\n+          options.features :\n+      null;\n+\n+  /**\n+   * @type {ol.Collection.<goog.events.Key>}\n+   * @private\n+   */\n+  this.featuresListenerKeys_ = new ol.Collection();\n+\n+  /**\n+   * @type {Object.<number, goog.events.Key>}\n+   * @private\n+   */\n+  this.geometryListenerKeys_ = {};\n+\n+  /**\n+   * Extents are preserved so indexed segment can be quickly removed\n+   * when its feature geometry changes\n+   * @type {Object.<number, ol.Extent>}\n+   * @private\n+   */\n+  this.indexedFeaturesExtents_ = {};\n+\n+  /**\n+   * @type {number}\n+   * @private\n+   */\n+  this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?\n+      options.pixelTolerance : 10;\n+\n+\n+  /**\n+  * Segment RTree for each layer\n+  * @type {Object.<*, ol.structs.RBush>}\n+  * @private\n+  */\n+  this.rBush_ = new ol.structs.RBush();\n+\n+\n+  /**\n+  * @const\n+  * @private\n+  * @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> }\n+  */\n+  this.SEGMENT_WRITERS_ = {\n+    'Point': this.writePointGeometry_,\n+    'LineString': this.writeLineStringGeometry_,\n+    'LinearRing': this.writeLineStringGeometry_,\n+    'Polygon': this.writePolygonGeometry_,\n+    'MultiPoint': this.writeMultiPointGeometry_,\n+    'MultiLineString': this.writeMultiLineStringGeometry_,\n+    'MultiPolygon': this.writeMultiPolygonGeometry_,\n+    'GeometryCollection': this.writeGeometryCollectionGeometry_\n+  };\n+};\n+goog.inherits(ol.interaction.Snap, ol.interaction.Pointer);\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature.\n+ * @param {boolean=} opt_listen Whether to listen to the geometry change or not\n+ *     Defaults to `true`.\n+ * @api\n+ */\n+ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {\n+  var listen = goog.isDef(opt_listen) ? opt_listen : true;\n+  var geometry = feature.getGeometry();\n+  var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];\n+  if (goog.isDef(segmentWriter)) {\n+    var feature_uid = goog.getUid(feature);\n+    this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent();\n+    segmentWriter.call(this, feature, geometry);\n+  }\n+\n+  if (listen) {\n+    var geom_uid = goog.getUid(geometry);\n+    this.geometryListenerKeys_[geom_uid] = geometry.on(\n+        goog.events.EventType.CHANGE,\n+        goog.bind(this.handleGeometryChanged_, this, feature),\n+        this);\n+  }\n+};\n+goog.exportProperty(\n+    ol.interaction.Snap.prototype,\n+    'addFeature',\n+    ol.interaction.Snap.prototype.addFeature);\n+\n+\n+/**\n+ * If a feature geometry changes while a pointer drag|move event occurs, the\n+ * feature doesn't get updated right away.  It will be at the next 'pointerup'\n+ * event fired.\n+ * @type {Object.<number, ol.Feature>}\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.pendingFeatures_ = null;\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {\n+  this.addFeature(feature);\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {\n+  this.removeFeature(feature);\n+};\n+\n+\n+/**\n+ * @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>}\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.getFeatures_ = function() {\n+  var features;\n+  if (!goog.isNull(this.features_)) {\n+    features = this.features_;\n+  } else if (!goog.isNull(this.source_)) {\n+    features = this.source_.getFeatures();\n+  }\n+  goog.asserts.assert(goog.isDef(features));\n+  return features;\n+};\n+\n+\n+/**\n+ * Handle all pointer events events.\n+ * @param {ol.MapBrowserEvent} evt A move event.\n+ * @return {boolean} Pass the event to other interactions.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.handleEvent_ = function(evt) {\n+  var result = this.snapTo(evt.pixel, evt.coordinate, evt.map);\n+  if (result.snapped) {\n+    evt.coordinate = result.vertex;\n+    evt.pixel = result.vertexPixel;\n+  }\n+  return true;\n+};\n+\n+\n+/**\n+ * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) {\n+  var feature;\n+  if (evt instanceof ol.source.VectorEvent) {\n+    feature = evt.feature;\n+  } else if (evt instanceof ol.CollectionEvent) {\n+    feature = evt.element;\n+  }\n+  goog.asserts.assertInstanceof(feature, ol.Feature);\n+  this.addFeature(feature);\n+};\n+\n+\n+/**\n+ * @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) {\n+  var feature;\n+  if (evt instanceof ol.source.VectorEvent) {\n+    feature = evt.feature;\n+  } else if (evt instanceof ol.CollectionEvent) {\n+    feature = evt.element;\n+  }\n+  goog.asserts.assertInstanceof(feature, ol.Feature);\n+  this.removeFeature(feature);\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature which geometry changed.\n+ * @param {goog.events.Event} evt Event.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.handleGeometryChanged_ = function(feature, evt) {\n+  if (this.handlingDownUpSequence) {\n+    var uid = goog.getUid(feature);\n+    if (!(uid in this.pendingFeatures_)) {\n+      this.pendingFeatures_[uid] = feature;\n+    }\n+  } else {\n+    this.updateFeature_(feature);\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {boolean=} opt_unlisten Whether to unlisten to the geometry change\n+ *     or not. Defaults to `true`.\n+ * @api\n+ */\n+ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) {\n+  var unlisten = goog.isDef(opt_unlisten) ? opt_unlisten : true;\n+  var feature_uid = goog.getUid(feature);\n+  var extent = this.indexedFeaturesExtents_[feature_uid];\n+  goog.asserts.assertArray(extent);\n+  var rBush = this.rBush_;\n+  var i, nodesToRemove = [];\n+  rBush.forEachInExtent(extent, function(node) {\n+    if (feature === node.feature) {\n+      nodesToRemove.push(node);\n+    }\n+  });\n+  for (i = nodesToRemove.length - 1; i >= 0; --i) {\n+    rBush.remove(nodesToRemove[i]);\n+  }\n+\n+  if (unlisten) {\n+    var geometry = feature.getGeometry();\n+    goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);\n+    var geom_uid = goog.getUid(geometry);\n+    ol.Observable.unByKey(this.geometryListenerKeys_[geom_uid]);\n+    delete this.geometryListenerKeys_[geom_uid];\n+  }\n+};\n+goog.exportProperty(\n+    ol.interaction.Snap.prototype,\n+    'removeFeature',\n+    ol.interaction.Snap.prototype.removeFeature);\n+\n+\n+/**\n+ * @inheritDoc\n+ */\n+ol.interaction.Snap.prototype.setMap = function(map) {\n+  var currentMap = this.getMap();\n+  var keys = this.featuresListenerKeys_;\n+  var features = this.getFeatures_();\n+\n+  if (currentMap) {\n+    keys.forEach(ol.Observable.unByKey, this);\n+    keys.clear();\n+    features.forEach(this.forEachFeatureRemove_, this);\n+  }\n+\n+  goog.base(this, 'setMap', map);\n+\n+  if (map) {\n+    if (!goog.isNull(this.features_)) {\n+      keys.push(this.features_.on(ol.CollectionEventType.ADD,\n+          this.handleFeatureAdd_, this));\n+      keys.push(this.features_.on(ol.CollectionEventType.REMOVE,\n+          this.handleFeatureRemove_, this));\n+    } else if (!goog.isNull(this.source_)) {\n+      keys.push(this.source_.on(ol.source.VectorEventType.ADDFEATURE,\n+          this.handleFeatureAdd_, this));\n+      keys.push(this.source_.on(ol.source.VectorEventType.REMOVEFEATURE,\n+          this.handleFeatureRemove_, this));\n+    }\n+    features.forEach(this.forEachFeatureAdd_, this);\n+  }\n+};\n+\n+\n+/**\n+ * @inheritDoc\n+ */\n+ol.interaction.Snap.prototype.shouldStopEvent = goog.functions.FALSE;\n+\n+\n+/**\n+ * @param {ol.Pixel} pixel Pixel\n+ * @param {ol.Coordinate} pixelCoordinate Coordinate\n+ * @param {ol.Map} map Map.\n+ * @return {ol.interaction.Snap.ResultType} Snap result\n+ */\n+ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) {\n+\n+  var lowerLeft = map.getCoordinateFromPixel(\n+      [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);\n+  var upperRight = map.getCoordinateFromPixel(\n+      [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);\n+  var box = ol.extent.boundingExtent([lowerLeft, upperRight]);\n+\n+  var segments = this.rBush_.getInExtent(box);\n+  var snappedToVertex = false;\n+  var snapped = false;\n+  var vertex = null;\n+  var vertexPixel = null;\n+  if (segments.length > 0) {\n+    segments.sort(goog.partial(\n+        ol.interaction.Snap.sortByDistance, pixelCoordinate));\n+    var closestSegment = segments[0].segment;\n+    vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,\n+        closestSegment));\n+    vertexPixel = map.getPixelFromCoordinate(vertex);\n+    if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=\n+        this.pixelTolerance_) {\n+      snapped = true;\n+      var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);\n+      var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);\n+      var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);\n+      var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);\n+      var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));\n+      snappedToVertex = dist <= this.pixelTolerance_;\n+      if (snappedToVertex) {\n+        vertex = squaredDist1 > squaredDist2 ?\n+            closestSegment[1] : closestSegment[0];\n+        vertexPixel = map.getPixelFromCoordinate(vertex);\n+        vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];\n+      }\n+    }\n+  }\n+  return /** @type {ol.interaction.Snap.ResultType} */ ({\n+    snapped: snapped,\n+    vertex: vertex,\n+    vertexPixel: vertexPixel\n+  });\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.updateFeature_ = function(feature) {\n+  console.log(3);\n+  this.removeFeature(feature, false);\n+  this.addFeature(feature, false);\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.GeometryCollection} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ =\n+    function(feature, geometry) {\n+  var i, geometries = geometry.getGeometriesArray();\n+  for (i = 0; i < geometries.length; ++i) {\n+    this.SEGMENT_WRITERS_[geometries[i].getType()].call(\n+        this, feature, geometries[i]);\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.LineString} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writeLineStringGeometry_ =\n+    function(feature, geometry) {\n+  var coordinates = geometry.getCoordinates();\n+  var i, ii, segment, segmentData;\n+  for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {\n+    segment = coordinates.slice(i, i + 2);\n+    segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+      feature: feature,\n+      segment: segment\n+    });\n+    this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.MultiLineString} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ =\n+    function(feature, geometry) {\n+  var lines = geometry.getCoordinates();\n+  var coordinates, i, ii, j, jj, segment, segmentData;\n+  for (j = 0, jj = lines.length; j < jj; ++j) {\n+    coordinates = lines[j];\n+    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {\n+      segment = coordinates.slice(i, i + 2);\n+      segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+        feature: feature,\n+        segment: segment\n+      });\n+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);\n+    }\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.MultiPoint} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writeMultiPointGeometry_ =\n+    function(feature, geometry) {\n+  var points = geometry.getCoordinates();\n+  var coordinates, i, ii, segmentData;\n+  for (i = 0, ii = points.length; i < ii; ++i) {\n+    coordinates = points[i];\n+    segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+      feature: feature,\n+      segment: [coordinates, coordinates]\n+    });\n+    this.rBush_.insert(geometry.getExtent(), segmentData);\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.MultiPolygon} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ =\n+    function(feature, geometry) {\n+  var polygons = geometry.getCoordinates();\n+  var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;\n+  for (k = 0, kk = polygons.length; k < kk; ++k) {\n+    rings = polygons[k];\n+    for (j = 0, jj = rings.length; j < jj; ++j) {\n+      coordinates = rings[j];\n+      for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {\n+        segment = coordinates.slice(i, i + 2);\n+        segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+          feature: feature,\n+          segment: segment\n+        });\n+        this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);\n+      }\n+    }\n+  }\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.Point} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writePointGeometry_ =\n+    function(feature, geometry) {\n+  var coordinates = geometry.getCoordinates();\n+  var segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+    feature: feature,\n+    segment: [coordinates, coordinates]\n+  });\n+  this.rBush_.insert(geometry.getExtent(), segmentData);\n+};\n+\n+\n+/**\n+ * @param {ol.Feature} feature Feature\n+ * @param {ol.geom.Polygon} geometry Geometry.\n+ * @private\n+ */\n+ol.interaction.Snap.prototype.writePolygonGeometry_ =\n+    function(feature, geometry) {\n+  var rings = geometry.getCoordinates();\n+  var coordinates, i, ii, j, jj, segment, segmentData;\n+  for (j = 0, jj = rings.length; j < jj; ++j) {\n+    coordinates = rings[j];\n+    for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {\n+      segment = coordinates.slice(i, i + 2);\n+      segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({\n+        feature: feature,\n+        segment: segment\n+      });\n+      this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);\n+    }\n+  }\n+};\n+\n+\n+/**\n+ * @typedef {{\n+ *     snapped: {boolean},\n+ *     vertex: (ol.Coordinate|null),\n+ *     vertexPixel: (ol.Pixel|null)\n+ * }}\n+ */\n+ol.interaction.Snap.ResultType;\n+\n+\n+/**\n+ * @typedef {{\n+ *     feature: ol.Feature,\n+ *     segment: Array.<ol.Coordinate>\n+ * }}\n+ */\n+ol.interaction.Snap.SegmentDataType;\n+\n+\n+/**\n+ * @param {ol.MapBrowserPointerEvent} evt Event.\n+ * @return {boolean} Stop drag sequence?\n+ * @this {ol.interaction.Snap}\n+ * @api\n+ */\n+ol.interaction.Snap.handleUpEvent = function(evt) {\n+  goog.array.forEach(goog.object.getValues(this.pendingFeatures_),\n+      this.updateFeature_, this);\n+  this.pendingFeatures_ = {};\n+  return false;\n+};\n+\n+\n+/**\n+ * @param {ol.MapBrowserEvent} mapBrowserEvent Map browser event.\n+ * @return {boolean} `false` to stop event propagation.\n+ * @this {ol.interaction.Snap}\n+ * @api","path":"src/ol/interaction/snapinteraction.js","position":585,"original_position":585,"commit_id":"ebc18b4f682e1dd1b53f2e5989dbbedd39123dcc","original_commit_id":"ebc18b4f682e1dd1b53f2e5989dbbedd39123dcc","user":{"login":"ahocevar","id":211514,"avatar_url":"https://avatars.githubusercontent.com/u/211514?v=3","gravatar_id":"","url":"https://api.github.com/users/ahocevar","html_url":"https://github.com/ahocevar","followers_url":"https://api.github.com/users/ahocevar/followers","following_url":"https://api.github.com/users/ahocevar/following{/other_user}","gists_url":"https://api.github.com/users/ahocevar/gists{/gist_id}","starred_url":"https://api.github.com/users/ahocevar/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/ahocevar/subscriptions","organizations_url":"https://api.github.com/users/ahocevar/orgs","repos_url":"https://api.github.com/users/ahocevar/repos","events_url":"https://api.github.com/users/ahocevar/events{/privacy}","received_events_url":"https://api.github.com/users/ahocevar/received_events","type":"User","site_admin":false},"body":"Or it could even be a member method instead of a static function.","created_at":"2015-03-27T09:03:37Z","updated_at":"2015-03-27T09:03:37Z","html_url":"https://github.com/openlayers/ol3/pull/3109#discussion_r27282278","pull_request_url":"https://api.github.com/repos/openlayers/ol3/pulls/3109","_links":{"self":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/comments/27282278"},"html":{"href":"https://github.com/openlayers/ol3/pull/3109#discussion_r27282278"},"pull_request":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/3109"}}},"pull_request":{"url":"https://api.github.com/repos/openlayers/ol3/pulls/3109","id":27107449,"html_url":"https://github.com/openlayers/ol3/pull/3109","diff_url":"https://github.com/openlayers/ol3/pull/3109.diff","patch_url":"https://github.com/openlayers/ol3/pull/3109.patch","issue_url":"https://api.github.com/repos/openlayers/ol3/issues/3109","number":3109,"state":"open","locked":false,"title":"Snap feature","user":{"login":"fperucic","id":10003528,"avatar_url":"https://avatars.githubusercontent.com/u/10003528?v=3","gravatar_id":"","url":"https://api.github.com/users/fperucic","html_url":"https://github.com/fperucic","followers_url":"https://api.github.com/users/fperucic/followers","following_url":"https://api.github.com/users/fperucic/following{/other_user}","gists_url":"https://api.github.com/users/fperucic/gists{/gist_id}","starred_url":"https://api.github.com/users/fperucic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fperucic/subscriptions","organizations_url":"https://api.github.com/users/fperucic/orgs","repos_url":"https://api.github.com/users/fperucic/repos","events_url":"https://api.github.com/users/fperucic/events{/privacy}","received_events_url":"https://api.github.com/users/fperucic/received_events","type":"User","site_admin":false},"body":"This is my implementation of a snap control discussed in #1164 \r\n\r\nIt uses simmilar snap implementation as ol.interaction.modify so you need to be careful when modifying the original geometry. Otherwise problems my occur as in #3093 where the user is not paying attention to rebuild the snap index of the modify interaction upon dragging the original geometry.\r\n\r\nHere is the [demo](http://server2.prometiprostor.hr/fperucic/ol-hosted/snap-feature/examples/snap.html?q=snap)","created_at":"2015-01-09T15:39:58Z","updated_at":"2015-03-27T09:03:37Z","closed_at":null,"merged_at":null,"merge_commit_sha":"fc10294d49eb6f14527ef17724b332c1120b3d66","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/openlayers/ol3/pulls/3109/commits","review_comments_url":"https://api.github.com/repos/openlayers/ol3/pulls/3109/comments","review_comment_url":"https://api.github.com/repos/openlayers/ol3/pulls/comments{/number}","comments_url":"https://api.github.com/repos/openlayers/ol3/issues/3109/comments","statuses_url":"https://api.github.com/repos/openlayers/ol3/statuses/ebc18b4f682e1dd1b53f2e5989dbbedd39123dcc","head":{"label":"fperucic:snap-feature","ref":"snap-feature","sha":"ebc18b4f682e1dd1b53f2e5989dbbedd39123dcc","user":{"login":"fperucic","id":10003528,"avatar_url":"https://avatars.githubusercontent.com/u/10003528?v=3","gravatar_id":"","url":"https://api.github.com/users/fperucic","html_url":"https://github.com/fperucic","followers_url":"https://api.github.com/users/fperucic/followers","following_url":"https://api.github.com/users/fperucic/following{/other_user}","gists_url":"https://api.github.com/users/fperucic/gists{/gist_id}","starred_url":"https://api.github.com/users/fperucic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fperucic/subscriptions","organizations_url":"https://api.github.com/users/fperucic/orgs","repos_url":"https://api.github.com/users/fperucic/repos","events_url":"https://api.github.com/users/fperucic/events{/privacy}","received_events_url":"https://api.github.com/users/fperucic/received_events","type":"User","site_admin":false},"repo":{"id":28672349,"name":"ol3","full_name":"fperucic/ol3","owner":{"login":"fperucic","id":10003528,"avatar_url":"https://avatars.githubusercontent.com/u/10003528?v=3","gravatar_id":"","url":"https://api.github.com/users/fperucic","html_url":"https://github.com/fperucic","followers_url":"https://api.github.com/users/fperucic/followers","following_url":"https://api.github.com/users/fperucic/following{/other_user}","gists_url":"https://api.github.com/users/fperucic/gists{/gist_id}","starred_url":"https://api.github.com/users/fperucic/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/fperucic/subscriptions","organizations_url":"https://api.github.com/users/fperucic/orgs","repos_url":"https://api.github.com/users/fperucic/repos","events_url":"https://api.github.com/users/fperucic/events{/privacy}","received_events_url":"https://api.github.com/users/fperucic/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/fperucic/ol3","description":"OpenLayers 3","fork":true,"url":"https://api.github.com/repos/fperucic/ol3","forks_url":"https://api.github.com/repos/fperucic/ol3/forks","keys_url":"https://api.github.com/repos/fperucic/ol3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/fperucic/ol3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/fperucic/ol3/teams","hooks_url":"https://api.github.com/repos/fperucic/ol3/hooks","issue_events_url":"https://api.github.com/repos/fperucic/ol3/issues/events{/number}","events_url":"https://api.github.com/repos/fperucic/ol3/events","assignees_url":"https://api.github.com/repos/fperucic/ol3/assignees{/user}","branches_url":"https://api.github.com/repos/fperucic/ol3/branches{/branch}","tags_url":"https://api.github.com/repos/fperucic/ol3/tags","blobs_url":"https://api.github.com/repos/fperucic/ol3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/fperucic/ol3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/fperucic/ol3/git/refs{/sha}","trees_url":"https://api.github.com/repos/fperucic/ol3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/fperucic/ol3/statuses/{sha}","languages_url":"https://api.github.com/repos/fperucic/ol3/languages","stargazers_url":"https://api.github.com/repos/fperucic/ol3/stargazers","contributors_url":"https://api.github.com/repos/fperucic/ol3/contributors","subscribers_url":"https://api.github.com/repos/fperucic/ol3/subscribers","subscription_url":"https://api.github.com/repos/fperucic/ol3/subscription","commits_url":"https://api.github.com/repos/fperucic/ol3/commits{/sha}","git_commits_url":"https://api.github.com/repos/fperucic/ol3/git/commits{/sha}","comments_url":"https://api.github.com/repos/fperucic/ol3/comments{/number}","issue_comment_url":"https://api.github.com/repos/fperucic/ol3/issues/comments{/number}","contents_url":"https://api.github.com/repos/fperucic/ol3/contents/{+path}","compare_url":"https://api.github.com/repos/fperucic/ol3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/fperucic/ol3/merges","archive_url":"https://api.github.com/repos/fperucic/ol3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/fperucic/ol3/downloads","issues_url":"https://api.github.com/repos/fperucic/ol3/issues{/number}","pulls_url":"https://api.github.com/repos/fperucic/ol3/pulls{/number}","milestones_url":"https://api.github.com/repos/fperucic/ol3/milestones{/number}","notifications_url":"https://api.github.com/repos/fperucic/ol3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/fperucic/ol3/labels{/name}","releases_url":"https://api.github.com/repos/fperucic/ol3/releases{/id}","created_at":"2014-12-31T18:29:23Z","updated_at":"2015-03-26T19:45:39Z","pushed_at":"2015-03-26T22:56:57Z","git_url":"git://github.com/fperucic/ol3.git","ssh_url":"git@github.com:fperucic/ol3.git","clone_url":"https://github.com/fperucic/ol3.git","svn_url":"https://github.com/fperucic/ol3","homepage":"http://openlayers.org","size":27584,"stargazers_count":0,"watchers_count":0,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":2,"forks":0,"open_issues":2,"watchers":0,"default_branch":"master"}},"base":{"label":"openlayers:master","ref":"master","sha":"7d20e2a3176ffb6ff74a1901d48b2ff2781cc22a","user":{"login":"openlayers","id":240579,"avatar_url":"https://avatars.githubusercontent.com/u/240579?v=3","gravatar_id":"","url":"https://api.github.com/users/openlayers","html_url":"https://github.com/openlayers","followers_url":"https://api.github.com/users/openlayers/followers","following_url":"https://api.github.com/users/openlayers/following{/other_user}","gists_url":"https://api.github.com/users/openlayers/gists{/gist_id}","starred_url":"https://api.github.com/users/openlayers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openlayers/subscriptions","organizations_url":"https://api.github.com/users/openlayers/orgs","repos_url":"https://api.github.com/users/openlayers/repos","events_url":"https://api.github.com/users/openlayers/events{/privacy}","received_events_url":"https://api.github.com/users/openlayers/received_events","type":"Organization","site_admin":false},"repo":{"id":4723248,"name":"ol3","full_name":"openlayers/ol3","owner":{"login":"openlayers","id":240579,"avatar_url":"https://avatars.githubusercontent.com/u/240579?v=3","gravatar_id":"","url":"https://api.github.com/users/openlayers","html_url":"https://github.com/openlayers","followers_url":"https://api.github.com/users/openlayers/followers","following_url":"https://api.github.com/users/openlayers/following{/other_user}","gists_url":"https://api.github.com/users/openlayers/gists{/gist_id}","starred_url":"https://api.github.com/users/openlayers/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/openlayers/subscriptions","organizations_url":"https://api.github.com/users/openlayers/orgs","repos_url":"https://api.github.com/users/openlayers/repos","events_url":"https://api.github.com/users/openlayers/events{/privacy}","received_events_url":"https://api.github.com/users/openlayers/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/openlayers/ol3","description":"OpenLayers 3","fork":false,"url":"https://api.github.com/repos/openlayers/ol3","forks_url":"https://api.github.com/repos/openlayers/ol3/forks","keys_url":"https://api.github.com/repos/openlayers/ol3/keys{/key_id}","collaborators_url":"https://api.github.com/repos/openlayers/ol3/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/openlayers/ol3/teams","hooks_url":"https://api.github.com/repos/openlayers/ol3/hooks","issue_events_url":"https://api.github.com/repos/openlayers/ol3/issues/events{/number}","events_url":"https://api.github.com/repos/openlayers/ol3/events","assignees_url":"https://api.github.com/repos/openlayers/ol3/assignees{/user}","branches_url":"https://api.github.com/repos/openlayers/ol3/branches{/branch}","tags_url":"https://api.github.com/repos/openlayers/ol3/tags","blobs_url":"https://api.github.com/repos/openlayers/ol3/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/openlayers/ol3/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/openlayers/ol3/git/refs{/sha}","trees_url":"https://api.github.com/repos/openlayers/ol3/git/trees{/sha}","statuses_url":"https://api.github.com/repos/openlayers/ol3/statuses/{sha}","languages_url":"https://api.github.com/repos/openlayers/ol3/languages","stargazers_url":"https://api.github.com/repos/openlayers/ol3/stargazers","contributors_url":"https://api.github.com/repos/openlayers/ol3/contributors","subscribers_url":"https://api.github.com/repos/openlayers/ol3/subscribers","subscription_url":"https://api.github.com/repos/openlayers/ol3/subscription","commits_url":"https://api.github.com/repos/openlayers/ol3/commits{/sha}","git_commits_url":"https://api.github.com/repos/openlayers/ol3/git/commits{/sha}","comments_url":"https://api.github.com/repos/openlayers/ol3/comments{/number}","issue_comment_url":"https://api.github.com/repos/openlayers/ol3/issues/comments{/number}","contents_url":"https://api.github.com/repos/openlayers/ol3/contents/{+path}","compare_url":"https://api.github.com/repos/openlayers/ol3/compare/{base}...{head}","merges_url":"https://api.github.com/repos/openlayers/ol3/merges","archive_url":"https://api.github.com/repos/openlayers/ol3/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/openlayers/ol3/downloads","issues_url":"https://api.github.com/repos/openlayers/ol3/issues{/number}","pulls_url":"https://api.github.com/repos/openlayers/ol3/pulls{/number}","milestones_url":"https://api.github.com/repos/openlayers/ol3/milestones{/number}","notifications_url":"https://api.github.com/repos/openlayers/ol3/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/openlayers/ol3/labels{/name}","releases_url":"https://api.github.com/repos/openlayers/ol3/releases{/id}","created_at":"2012-06-20T06:53:59Z","updated_at":"2015-03-26T19:18:13Z","pushed_at":"2015-03-26T22:56:58Z","git_url":"git://github.com/openlayers/ol3.git","ssh_url":"git@github.com:openlayers/ol3.git","clone_url":"https://github.com/openlayers/ol3.git","svn_url":"https://github.com/openlayers/ol3","homepage":"http://openlayers.org","size":69938,"stargazers_count":1052,"watchers_count":1052,"language":"JavaScript","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":624,"mirror_url":null,"open_issues_count":297,"forks":624,"open_issues":297,"watchers":1052,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/3109"},"html":{"href":"https://github.com/openlayers/ol3/pull/3109"},"issue":{"href":"https://api.github.com/repos/openlayers/ol3/issues/3109"},"comments":{"href":"https://api.github.com/repos/openlayers/ol3/issues/3109/comments"},"review_comments":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/3109/comments"},"review_comment":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/openlayers/ol3/pulls/3109/commits"},"statuses":{"href":"https://api.github.com/repos/openlayers/ol3/statuses/ebc18b4f682e1dd1b53f2e5989dbbedd39123dcc"}}}}