77
examples/snap.html
Normal file
77
examples/snap.html
Normal file
@@ -0,0 +1,77 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="chrome=1">
|
||||
<meta name="viewport" content="initial-scale=1.0, user-scalable=no, width=device-width">
|
||||
<link rel="stylesheet" href="../css/ol.css" type="text/css">
|
||||
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap.min.css" type="text/css">
|
||||
<link rel="stylesheet" href="../resources/layout.css" type="text/css">
|
||||
<link rel="stylesheet" href="../resources/bootstrap/css/bootstrap-responsive.min.css" type="text/css">
|
||||
<title> Snap example </title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div class="navbar navbar-inverse navbar-fixed-top">
|
||||
<div class="navbar-inner">
|
||||
<div class="container">
|
||||
<a class="brand" href="./"><img src="../resources/logo.png"> OpenLayers 3 Examples</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="row-fluid">
|
||||
<div class="span12">
|
||||
<div id="map" class="map"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row-fluid">
|
||||
|
||||
<div class="span12">
|
||||
<h4 id="title">Snap interaction example</h4>
|
||||
<p id="shortdesc">Example of using the snap interaction together with
|
||||
draw and modify interactions. The snap interaction must be added
|
||||
last, as it needs to be the first to handle the
|
||||
<code>pointermove</code> event.</p>
|
||||
<form id="options-form" automplete="off">
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="interaction" value="draw" id="draw" checked>
|
||||
Draw
|
||||
</label>
|
||||
</div>
|
||||
<div class="radio">
|
||||
<label>
|
||||
<input type="radio" name="interaction" value="modify">
|
||||
Modify
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Draw type </label>
|
||||
<select name="draw-type" id="draw-type">
|
||||
<option value="Point">Point</option>
|
||||
<option value="LineString">LineString</option>
|
||||
<option value="Polygon">Polygon</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div id="docs">
|
||||
<p>See the <a href="snap.js" target="_blank">snap.js source</a> to see how this is done.</p>
|
||||
</div>
|
||||
<div id="tags">draw, edit, modify, vector, featureoverlay, snap</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script src="../resources/jquery.min.js" type="text/javascript"></script>
|
||||
<script src="../resources/example-behaviour.js" type="text/javascript"></script>
|
||||
<script src="loader.js?id=snap" type="text/javascript"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
149
examples/snap.js
Normal file
149
examples/snap.js
Normal file
@@ -0,0 +1,149 @@
|
||||
goog.require('ol.FeatureOverlay');
|
||||
goog.require('ol.Map');
|
||||
goog.require('ol.View');
|
||||
goog.require('ol.interaction');
|
||||
goog.require('ol.interaction.Draw');
|
||||
goog.require('ol.interaction.Modify');
|
||||
goog.require('ol.interaction.Select');
|
||||
goog.require('ol.interaction.Snap');
|
||||
goog.require('ol.layer.Tile');
|
||||
goog.require('ol.source.MapQuest');
|
||||
goog.require('ol.style.Circle');
|
||||
goog.require('ol.style.Fill');
|
||||
goog.require('ol.style.Stroke');
|
||||
goog.require('ol.style.Style');
|
||||
|
||||
var raster = new ol.layer.Tile({
|
||||
source: new ol.source.MapQuest({layer: 'sat'})
|
||||
});
|
||||
|
||||
var map = new ol.Map({
|
||||
layers: [raster],
|
||||
target: 'map',
|
||||
view: new ol.View({
|
||||
center: [-11000000, 4600000],
|
||||
zoom: 4
|
||||
})
|
||||
});
|
||||
|
||||
// The features are not added to a regular vector layer/source,
|
||||
// but to a feature overlay which holds a collection of features.
|
||||
// This collection is passed to the modify and also the draw
|
||||
// interaction, so that both can add or modify features.
|
||||
var featureOverlay = new ol.FeatureOverlay({
|
||||
style: new ol.style.Style({
|
||||
fill: new ol.style.Fill({
|
||||
color: 'rgba(255, 255, 255, 0.2)'
|
||||
}),
|
||||
stroke: new ol.style.Stroke({
|
||||
color: '#ffcc33',
|
||||
width: 2
|
||||
}),
|
||||
image: new ol.style.Circle({
|
||||
radius: 7,
|
||||
fill: new ol.style.Fill({
|
||||
color: '#ffcc33'
|
||||
})
|
||||
})
|
||||
}),
|
||||
map: map
|
||||
});
|
||||
|
||||
var Modify = {
|
||||
init: function() {
|
||||
this.select = new ol.interaction.Select();
|
||||
map.addInteraction(this.select);
|
||||
|
||||
this.modify = new ol.interaction.Modify({
|
||||
features: this.select.getFeatures()
|
||||
});
|
||||
map.addInteraction(this.modify);
|
||||
|
||||
this.setEvents();
|
||||
},
|
||||
setEvents: function() {
|
||||
var selectedFeatures = this.select.getFeatures();
|
||||
|
||||
this.select.on('change:active', function() {
|
||||
selectedFeatures.forEach(selectedFeatures.remove, selectedFeatures);
|
||||
});
|
||||
},
|
||||
setActive: function(active) {
|
||||
this.select.setActive(active);
|
||||
this.modify.setActive(active);
|
||||
}
|
||||
};
|
||||
Modify.init();
|
||||
|
||||
|
||||
var Draw = {
|
||||
init: function() {
|
||||
map.addInteraction(this.Point);
|
||||
this.Point.setActive(false);
|
||||
map.addInteraction(this.LineString);
|
||||
this.LineString.setActive(false);
|
||||
map.addInteraction(this.Polygon);
|
||||
this.Polygon.setActive(false);
|
||||
},
|
||||
Point: new ol.interaction.Draw({
|
||||
features: featureOverlay.getFeatures(),
|
||||
type: /** @type {ol.geom.GeometryType} */ ('Point')
|
||||
}),
|
||||
LineString: new ol.interaction.Draw({
|
||||
features: featureOverlay.getFeatures(),
|
||||
type: /** @type {ol.geom.GeometryType} */ ('LineString')
|
||||
}),
|
||||
Polygon: new ol.interaction.Draw({
|
||||
features: featureOverlay.getFeatures(),
|
||||
type: /** @type {ol.geom.GeometryType} */ ('Polygon')
|
||||
}),
|
||||
getActive: function() {
|
||||
return this.activeType ? this[this.activeType].getActive() : false;
|
||||
},
|
||||
setActive: function(active) {
|
||||
var type = optionsForm.elements['draw-type'].value;
|
||||
if (active) {
|
||||
this.activeType && this[this.activeType].setActive(false);
|
||||
this[type].setActive(true);
|
||||
this.activeType = type;
|
||||
} else {
|
||||
this.activeType && this[this.activeType].setActive(false);
|
||||
this.activeType = null;
|
||||
}
|
||||
}
|
||||
};
|
||||
Draw.init();
|
||||
|
||||
var optionsForm = document.getElementById('options-form');
|
||||
|
||||
|
||||
/**
|
||||
* Let user change the geometry type.
|
||||
* @param {Event} e Change event.
|
||||
*/
|
||||
optionsForm.onchange = function(e) {
|
||||
var type = e.target.getAttribute('name');
|
||||
var value = e.target.value;
|
||||
if (type == 'draw-type') {
|
||||
Draw.getActive() && Draw.setActive(true);
|
||||
} else if (type == 'interaction') {
|
||||
if (value == 'modify') {
|
||||
Draw.setActive(false);
|
||||
Modify.setActive(true);
|
||||
} else if (value == 'draw') {
|
||||
Draw.setActive(true);
|
||||
Modify.setActive(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Draw.setActive(true);
|
||||
Modify.setActive(false);
|
||||
|
||||
// The snap interaction must be added after the Modify and Draw interactions
|
||||
// in order for its map browser event handlers to be fired first. Its handlers
|
||||
// are responsible of doing the snapping.
|
||||
var snap = new ol.interaction.Snap({
|
||||
features: featureOverlay.getFeatures()
|
||||
});
|
||||
map.addInteraction(snap);
|
||||
@@ -2468,7 +2468,7 @@ olx.interaction.ModifyOptions.prototype.deleteCondition;
|
||||
|
||||
/**
|
||||
* Pixel tolerance for considering the pointer close enough to a segment or
|
||||
* vertex for editing. Default is `10` pixels.
|
||||
* vertex for editing.
|
||||
* @type {number|undefined}
|
||||
* @api
|
||||
*/
|
||||
@@ -2689,6 +2689,43 @@ olx.interaction.SelectOptions.prototype.toggleCondition;
|
||||
olx.interaction.SelectOptions.prototype.multi;
|
||||
|
||||
|
||||
/**
|
||||
* Options for snap
|
||||
* @typedef {{
|
||||
* features: (ol.Collection.<ol.Feature>|undefined),
|
||||
* pixelTolerance: (number|undefined),
|
||||
* source: (ol.source.Vector|undefined)
|
||||
* }}
|
||||
* @api
|
||||
*/
|
||||
olx.interaction.SnapOptions;
|
||||
|
||||
|
||||
/**
|
||||
* Snap to these features. Either this option or source should be provided.
|
||||
* @type {ol.Collection.<ol.Feature>|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.interaction.SnapOptions.prototype.features;
|
||||
|
||||
|
||||
/**
|
||||
* Pixel tolerance for considering the pointer close enough to a segment or
|
||||
* vertex for editing. Default is `10` pixels.
|
||||
* @type {number|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.interaction.SnapOptions.prototype.pixelTolerance;
|
||||
|
||||
|
||||
/**
|
||||
* Snap to features from this source. Either this option or features should be provided
|
||||
* @type {ol.source.Vector|undefined}
|
||||
* @api
|
||||
*/
|
||||
olx.interaction.SnapOptions.prototype.source;
|
||||
|
||||
|
||||
/**
|
||||
* Namespace.
|
||||
* @type {Object}
|
||||
|
||||
602
src/ol/interaction/snapinteraction.js
Normal file
602
src/ol/interaction/snapinteraction.js
Normal file
@@ -0,0 +1,602 @@
|
||||
goog.provide('ol.interaction.Snap');
|
||||
goog.provide('ol.interaction.SnapProperty');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('goog.object');
|
||||
goog.require('ol.Collection');
|
||||
goog.require('ol.CollectionEvent');
|
||||
goog.require('ol.CollectionEventType');
|
||||
goog.require('ol.Extent');
|
||||
goog.require('ol.Feature');
|
||||
goog.require('ol.Observable');
|
||||
goog.require('ol.coordinate');
|
||||
goog.require('ol.extent');
|
||||
goog.require('ol.geom.Geometry');
|
||||
goog.require('ol.interaction.Pointer');
|
||||
goog.require('ol.source.Vector');
|
||||
goog.require('ol.source.VectorEvent');
|
||||
goog.require('ol.source.VectorEventType');
|
||||
goog.require('ol.structs.RBush');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @classdesc
|
||||
* Handles snapping of vector features while modifying or drawing them. The
|
||||
* features can come from a {@link ol.source.Vector}, a {@link ol.Collection}
|
||||
* or a plain array. Any interaction object that allows the user to interact
|
||||
* with the features using the mouse can benefit from the snapping, as long
|
||||
* as it is added before.
|
||||
*
|
||||
* The snap interaction modifies map browser event `coordinate` and `pixel`
|
||||
* properties to force the snap to occur to any interaction that them.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* var snap = new ol.interaction.Snap({
|
||||
* source: source
|
||||
* });
|
||||
*
|
||||
* @constructor
|
||||
* @extends {ol.interaction.Pointer}
|
||||
* @param {olx.interaction.SnapOptions=} opt_options Options.
|
||||
* @api
|
||||
*/
|
||||
ol.interaction.Snap = function(opt_options) {
|
||||
|
||||
goog.base(this, {
|
||||
handleEvent: ol.interaction.Snap.handleEvent_,
|
||||
handleDownEvent: goog.functions.TRUE,
|
||||
handleUpEvent: ol.interaction.Snap.handleUpEvent_
|
||||
});
|
||||
|
||||
var options = goog.isDef(opt_options) ? opt_options : {};
|
||||
|
||||
/**
|
||||
* @type {ol.source.Vector}
|
||||
* @private
|
||||
*/
|
||||
this.source_ = goog.isDef(options.source) ? options.source : null;
|
||||
|
||||
/**
|
||||
* @type {ol.Collection.<ol.Feature>}
|
||||
* @private
|
||||
*/
|
||||
this.features_ = goog.isDef(options.features) ? options.features : null;
|
||||
|
||||
/**
|
||||
* @type {ol.Collection.<goog.events.Key>}
|
||||
* @private
|
||||
*/
|
||||
this.featuresListenerKeys_ = new ol.Collection();
|
||||
|
||||
/**
|
||||
* @type {Object.<number, goog.events.Key>}
|
||||
* @private
|
||||
*/
|
||||
this.geometryListenerKeys_ = {};
|
||||
|
||||
/**
|
||||
* Extents are preserved so indexed segment can be quickly removed
|
||||
* when its feature geometry changes
|
||||
* @type {Object.<number, ol.Extent>}
|
||||
* @private
|
||||
*/
|
||||
this.indexedFeaturesExtents_ = {};
|
||||
|
||||
/**
|
||||
* Used for distance sorting in sortByDistance_
|
||||
* @type {ol.Coordinate}
|
||||
* @private
|
||||
*/
|
||||
this.pixelCoordinate_ = null;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
* @private
|
||||
*/
|
||||
this.pixelTolerance_ = goog.isDef(options.pixelTolerance) ?
|
||||
options.pixelTolerance : 10;
|
||||
|
||||
/**
|
||||
* @type {Function}
|
||||
* @private
|
||||
*/
|
||||
this.sortByDistance_ = goog.bind(ol.interaction.Snap.sortByDistance, this);
|
||||
|
||||
|
||||
/**
|
||||
* Segment RTree for each layer
|
||||
* @type {Object.<*, ol.structs.RBush>}
|
||||
* @private
|
||||
*/
|
||||
this.rBush_ = new ol.structs.RBush();
|
||||
|
||||
|
||||
/**
|
||||
* @const
|
||||
* @private
|
||||
* @type {Object.<string, function(ol.Feature, ol.geom.Geometry)> }
|
||||
*/
|
||||
this.SEGMENT_WRITERS_ = {
|
||||
'Point': this.writePointGeometry_,
|
||||
'LineString': this.writeLineStringGeometry_,
|
||||
'LinearRing': this.writeLineStringGeometry_,
|
||||
'Polygon': this.writePolygonGeometry_,
|
||||
'MultiPoint': this.writeMultiPointGeometry_,
|
||||
'MultiLineString': this.writeMultiLineStringGeometry_,
|
||||
'MultiPolygon': this.writeMultiPolygonGeometry_,
|
||||
'GeometryCollection': this.writeGeometryCollectionGeometry_
|
||||
};
|
||||
};
|
||||
goog.inherits(ol.interaction.Snap, ol.interaction.Pointer);
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature.
|
||||
* @param {boolean=} opt_listen Whether to listen to the geometry change or not
|
||||
* Defaults to `true`.
|
||||
* @api
|
||||
*/
|
||||
ol.interaction.Snap.prototype.addFeature = function(feature, opt_listen) {
|
||||
var listen = goog.isDef(opt_listen) ? opt_listen : true;
|
||||
var geometry = feature.getGeometry();
|
||||
var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
|
||||
if (goog.isDef(segmentWriter)) {
|
||||
var feature_uid = goog.getUid(feature);
|
||||
this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent();
|
||||
segmentWriter.call(this, feature, geometry);
|
||||
}
|
||||
|
||||
if (listen) {
|
||||
var geom_uid = goog.getUid(geometry);
|
||||
this.geometryListenerKeys_[geom_uid] = geometry.on(
|
||||
goog.events.EventType.CHANGE,
|
||||
goog.bind(this.handleGeometryChanged_, this, feature),
|
||||
this);
|
||||
}
|
||||
};
|
||||
goog.exportProperty(
|
||||
ol.interaction.Snap.prototype,
|
||||
'addFeature',
|
||||
ol.interaction.Snap.prototype.addFeature);
|
||||
|
||||
|
||||
/**
|
||||
* If a feature geometry changes while a pointer drag|move event occurs, the
|
||||
* feature doesn't get updated right away. It will be at the next 'pointerup'
|
||||
* event fired.
|
||||
* @type {Object.<number, ol.Feature>}
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.pendingFeatures_ = null;
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.forEachFeatureAdd_ = function(feature) {
|
||||
this.addFeature(feature);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.forEachFeatureRemove_ = function(feature) {
|
||||
this.removeFeature(feature);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {ol.Collection.<ol.Feature>|Array.<ol.Feature>}
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.getFeatures_ = function() {
|
||||
var features;
|
||||
if (!goog.isNull(this.features_)) {
|
||||
features = this.features_;
|
||||
} else if (!goog.isNull(this.source_)) {
|
||||
features = this.source_.getFeatures();
|
||||
}
|
||||
goog.asserts.assert(goog.isDef(features));
|
||||
return features;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.handleFeatureAdd_ = function(evt) {
|
||||
var feature;
|
||||
if (evt instanceof ol.source.VectorEvent) {
|
||||
feature = evt.feature;
|
||||
} else if (evt instanceof ol.CollectionEvent) {
|
||||
feature = evt.element;
|
||||
}
|
||||
goog.asserts.assertInstanceof(feature, ol.Feature);
|
||||
this.addFeature(feature);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.source.VectorEvent|ol.CollectionEvent} evt Event.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.handleFeatureRemove_ = function(evt) {
|
||||
var feature;
|
||||
if (evt instanceof ol.source.VectorEvent) {
|
||||
feature = evt.feature;
|
||||
} else if (evt instanceof ol.CollectionEvent) {
|
||||
feature = evt.element;
|
||||
}
|
||||
goog.asserts.assertInstanceof(feature, ol.Feature);
|
||||
this.removeFeature(feature);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature which geometry changed.
|
||||
* @param {goog.events.Event} evt Event.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.handleGeometryChanged_ = function(feature, evt) {
|
||||
if (this.handlingDownUpSequence) {
|
||||
var uid = goog.getUid(feature);
|
||||
if (!(uid in this.pendingFeatures_)) {
|
||||
this.pendingFeatures_[uid] = feature;
|
||||
}
|
||||
} else {
|
||||
this.updateFeature_(feature);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {boolean=} opt_unlisten Whether to unlisten to the geometry change
|
||||
* or not. Defaults to `true`.
|
||||
* @api
|
||||
*/
|
||||
ol.interaction.Snap.prototype.removeFeature = function(feature, opt_unlisten) {
|
||||
var unlisten = goog.isDef(opt_unlisten) ? opt_unlisten : true;
|
||||
var feature_uid = goog.getUid(feature);
|
||||
var extent = this.indexedFeaturesExtents_[feature_uid];
|
||||
goog.asserts.assertArray(extent);
|
||||
var rBush = this.rBush_;
|
||||
var i, nodesToRemove = [];
|
||||
rBush.forEachInExtent(extent, function(node) {
|
||||
if (feature === node.feature) {
|
||||
nodesToRemove.push(node);
|
||||
}
|
||||
});
|
||||
for (i = nodesToRemove.length - 1; i >= 0; --i) {
|
||||
rBush.remove(nodesToRemove[i]);
|
||||
}
|
||||
|
||||
if (unlisten) {
|
||||
var geometry = feature.getGeometry();
|
||||
goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
|
||||
var geom_uid = goog.getUid(geometry);
|
||||
ol.Observable.unByKey(this.geometryListenerKeys_[geom_uid]);
|
||||
delete this.geometryListenerKeys_[geom_uid];
|
||||
}
|
||||
};
|
||||
goog.exportProperty(
|
||||
ol.interaction.Snap.prototype,
|
||||
'removeFeature',
|
||||
ol.interaction.Snap.prototype.removeFeature);
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.interaction.Snap.prototype.setMap = function(map) {
|
||||
var currentMap = this.getMap();
|
||||
var keys = this.featuresListenerKeys_;
|
||||
var features = this.getFeatures_();
|
||||
|
||||
if (currentMap) {
|
||||
keys.forEach(ol.Observable.unByKey, this);
|
||||
keys.clear();
|
||||
features.forEach(this.forEachFeatureRemove_, this);
|
||||
}
|
||||
|
||||
goog.base(this, 'setMap', map);
|
||||
|
||||
if (map) {
|
||||
if (!goog.isNull(this.features_)) {
|
||||
keys.push(this.features_.on(ol.CollectionEventType.ADD,
|
||||
this.handleFeatureAdd_, this));
|
||||
keys.push(this.features_.on(ol.CollectionEventType.REMOVE,
|
||||
this.handleFeatureRemove_, this));
|
||||
} else if (!goog.isNull(this.source_)) {
|
||||
keys.push(this.source_.on(ol.source.VectorEventType.ADDFEATURE,
|
||||
this.handleFeatureAdd_, this));
|
||||
keys.push(this.source_.on(ol.source.VectorEventType.REMOVEFEATURE,
|
||||
this.handleFeatureRemove_, this));
|
||||
}
|
||||
features.forEach(this.forEachFeatureAdd_, this);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
ol.interaction.Snap.prototype.shouldStopEvent = goog.functions.FALSE;
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Pixel} pixel Pixel
|
||||
* @param {ol.Coordinate} pixelCoordinate Coordinate
|
||||
* @param {ol.Map} map Map.
|
||||
* @return {ol.interaction.Snap.ResultType} Snap result
|
||||
*/
|
||||
ol.interaction.Snap.prototype.snapTo = function(pixel, pixelCoordinate, map) {
|
||||
|
||||
var lowerLeft = map.getCoordinateFromPixel(
|
||||
[pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
|
||||
var upperRight = map.getCoordinateFromPixel(
|
||||
[pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
|
||||
var box = ol.extent.boundingExtent([lowerLeft, upperRight]);
|
||||
|
||||
var segments = this.rBush_.getInExtent(box);
|
||||
var snappedToVertex = false;
|
||||
var snapped = false;
|
||||
var vertex = null;
|
||||
var vertexPixel = null;
|
||||
if (segments.length > 0) {
|
||||
this.pixelCoordinate_ = pixelCoordinate;
|
||||
segments.sort(this.sortByDistance_);
|
||||
var closestSegment = segments[0].segment;
|
||||
vertex = (ol.coordinate.closestOnSegment(pixelCoordinate,
|
||||
closestSegment));
|
||||
vertexPixel = map.getPixelFromCoordinate(vertex);
|
||||
if (Math.sqrt(ol.coordinate.squaredDistance(pixel, vertexPixel)) <=
|
||||
this.pixelTolerance_) {
|
||||
snapped = true;
|
||||
var pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
|
||||
var pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
|
||||
var squaredDist1 = ol.coordinate.squaredDistance(vertexPixel, pixel1);
|
||||
var squaredDist2 = ol.coordinate.squaredDistance(vertexPixel, pixel2);
|
||||
var dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
|
||||
snappedToVertex = dist <= this.pixelTolerance_;
|
||||
if (snappedToVertex) {
|
||||
vertex = squaredDist1 > squaredDist2 ?
|
||||
closestSegment[1] : closestSegment[0];
|
||||
vertexPixel = map.getPixelFromCoordinate(vertex);
|
||||
vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
|
||||
}
|
||||
}
|
||||
}
|
||||
return /** @type {ol.interaction.Snap.ResultType} */ ({
|
||||
snapped: snapped,
|
||||
vertex: vertex,
|
||||
vertexPixel: vertexPixel
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.updateFeature_ = function(feature) {
|
||||
this.removeFeature(feature, false);
|
||||
this.addFeature(feature, false);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.GeometryCollection} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writeGeometryCollectionGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var i, geometries = geometry.getGeometriesArray();
|
||||
for (i = 0; i < geometries.length; ++i) {
|
||||
this.SEGMENT_WRITERS_[geometries[i].getType()].call(
|
||||
this, feature, geometries[i]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.LineString} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writeLineStringGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var coordinates = geometry.getCoordinates();
|
||||
var i, ii, segment, segmentData;
|
||||
for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
||||
segment = coordinates.slice(i, i + 2);
|
||||
segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: segment
|
||||
});
|
||||
this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.MultiLineString} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writeMultiLineStringGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var lines = geometry.getCoordinates();
|
||||
var coordinates, i, ii, j, jj, segment, segmentData;
|
||||
for (j = 0, jj = lines.length; j < jj; ++j) {
|
||||
coordinates = lines[j];
|
||||
for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
||||
segment = coordinates.slice(i, i + 2);
|
||||
segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: segment
|
||||
});
|
||||
this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.MultiPoint} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writeMultiPointGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var points = geometry.getCoordinates();
|
||||
var coordinates, i, ii, segmentData;
|
||||
for (i = 0, ii = points.length; i < ii; ++i) {
|
||||
coordinates = points[i];
|
||||
segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: [coordinates, coordinates]
|
||||
});
|
||||
this.rBush_.insert(geometry.getExtent(), segmentData);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.MultiPolygon} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writeMultiPolygonGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var polygons = geometry.getCoordinates();
|
||||
var coordinates, i, ii, j, jj, k, kk, rings, segment, segmentData;
|
||||
for (k = 0, kk = polygons.length; k < kk; ++k) {
|
||||
rings = polygons[k];
|
||||
for (j = 0, jj = rings.length; j < jj; ++j) {
|
||||
coordinates = rings[j];
|
||||
for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
||||
segment = coordinates.slice(i, i + 2);
|
||||
segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: segment
|
||||
});
|
||||
this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.Point} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writePointGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var coordinates = geometry.getCoordinates();
|
||||
var segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: [coordinates, coordinates]
|
||||
});
|
||||
this.rBush_.insert(geometry.getExtent(), segmentData);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.Feature} feature Feature
|
||||
* @param {ol.geom.Polygon} geometry Geometry.
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.prototype.writePolygonGeometry_ =
|
||||
function(feature, geometry) {
|
||||
var rings = geometry.getCoordinates();
|
||||
var coordinates, i, ii, j, jj, segment, segmentData;
|
||||
for (j = 0, jj = rings.length; j < jj; ++j) {
|
||||
coordinates = rings[j];
|
||||
for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
|
||||
segment = coordinates.slice(i, i + 2);
|
||||
segmentData = /** @type {ol.interaction.Snap.SegmentDataType} */ ({
|
||||
feature: feature,
|
||||
segment: segment
|
||||
});
|
||||
this.rBush_.insert(ol.extent.boundingExtent(segment), segmentData);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* snapped: {boolean},
|
||||
* vertex: (ol.Coordinate|null),
|
||||
* vertexPixel: (ol.Pixel|null)
|
||||
* }}
|
||||
*/
|
||||
ol.interaction.Snap.ResultType;
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {{
|
||||
* feature: ol.Feature,
|
||||
* segment: Array.<ol.Coordinate>
|
||||
* }}
|
||||
*/
|
||||
ol.interaction.Snap.SegmentDataType;
|
||||
|
||||
|
||||
/**
|
||||
* Handle all pointer events events.
|
||||
* @param {ol.MapBrowserEvent} evt A move event.
|
||||
* @return {boolean} Pass the event to other interactions.
|
||||
* @this {ol.interaction.Snap}
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.handleEvent_ = function(evt) {
|
||||
var result = this.snapTo(evt.pixel, evt.coordinate, evt.map);
|
||||
if (result.snapped) {
|
||||
evt.coordinate = result.vertex;
|
||||
evt.pixel = result.vertexPixel;
|
||||
}
|
||||
return ol.interaction.Pointer.handleEvent.call(this, evt);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {ol.MapBrowserPointerEvent} evt Event.
|
||||
* @return {boolean} Stop drag sequence?
|
||||
* @this {ol.interaction.Snap}
|
||||
* @private
|
||||
*/
|
||||
ol.interaction.Snap.handleUpEvent_ = function(evt) {
|
||||
goog.array.forEach(goog.object.getValues(this.pendingFeatures_),
|
||||
this.updateFeature_, this);
|
||||
this.pendingFeatures_ = {};
|
||||
return false;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Sort segments by distance, helper function
|
||||
* @param {ol.interaction.Snap.SegmentDataType} a
|
||||
* @param {ol.interaction.Snap.SegmentDataType} b
|
||||
* @return {number}
|
||||
* @this {ol.interaction.Snap}
|
||||
*/
|
||||
ol.interaction.Snap.sortByDistance = function(a, b) {
|
||||
return ol.coordinate.squaredDistanceToSegment(
|
||||
this.pixelCoordinate_, a.segment) -
|
||||
ol.coordinate.squaredDistanceToSegment(
|
||||
this.pixelCoordinate_, b.segment);
|
||||
};
|
||||
Reference in New Issue
Block a user