c8y-openlayer
Version:
This module is designed to help integrate Openlayer with Cumulocity IoT
635 lines (561 loc) • 19.1 kB
JavaScript
import _ol_ from '../index.js';
import _ol_Collection_ from '../collection.js';
import _ol_CollectionEventType_ from '../collectioneventtype.js';
import _ol_coordinate_ from '../coordinate.js';
import _ol_events_ from '../events.js';
import _ol_events_EventType_ from '../events/eventtype.js';
import _ol_extent_ from '../extent.js';
import _ol_functions_ from '../functions.js';
import _ol_geom_GeometryType_ from '../geom/geometrytype.js';
import _ol_geom_Polygon_ from '../geom/polygon.js';
import _ol_interaction_Pointer_ from '../interaction/pointer.js';
import _ol_obj_ from '../obj.js';
import _ol_source_Vector_ from '../source/vector.js';
import _ol_source_VectorEventType_ from '../source/vectoreventtype.js';
import _ol_structs_RBush_ from '../structs/rbush.js';
/**
* @classdesc
* Handles snapping of vector features while modifying or drawing them. The
* features can come from a {@link ol.source.Vector} or {@link ol.Collection}
* 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
*/
var _ol_interaction_Snap_ = function(opt_options) {
_ol_interaction_Pointer_.call(this, {
handleEvent: _ol_interaction_Snap_.handleEvent_,
handleDownEvent: _ol_functions_.TRUE,
handleUpEvent: _ol_interaction_Snap_.handleUpEvent_
});
var options = opt_options ? opt_options : {};
/**
* @type {ol.source.Vector}
* @private
*/
this.source_ = options.source ? options.source : null;
/**
* @private
* @type {boolean}
*/
this.vertex_ = options.vertex !== undefined ? options.vertex : true;
/**
* @private
* @type {boolean}
*/
this.edge_ = options.edge !== undefined ? options.edge : true;
/**
* @type {ol.Collection.<ol.Feature>}
* @private
*/
this.features_ = options.features ? options.features : null;
/**
* @type {Array.<ol.EventsKey>}
* @private
*/
this.featuresListenerKeys_ = [];
/**
* @type {Object.<number, ol.EventsKey>}
* @private
*/
this.featureChangeListenerKeys_ = {};
/**
* Extents are preserved so indexed segment can be quickly removed
* when its feature geometry changes
* @type {Object.<number, ol.Extent>}
* @private
*/
this.indexedFeaturesExtents_ = {};
/**
* 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
*/
this.pendingFeatures_ = {};
/**
* Used for distance sorting in sortByDistance_
* @type {ol.Coordinate}
* @private
*/
this.pixelCoordinate_ = null;
/**
* @type {number}
* @private
*/
this.pixelTolerance_ = options.pixelTolerance !== undefined ?
options.pixelTolerance : 10;
/**
* @type {function(ol.SnapSegmentDataType, ol.SnapSegmentDataType): number}
* @private
*/
this.sortByDistance_ = _ol_interaction_Snap_.sortByDistance.bind(this);
/**
* Segment RTree for each layer
* @type {ol.structs.RBush.<ol.SnapSegmentDataType>}
* @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_,
'Circle': this.writeCircleGeometry_
};
};
_ol_.inherits(_ol_interaction_Snap_, _ol_interaction_Pointer_);
/**
* Add a feature to the collection of features that we may snap to.
* @param {ol.Feature} feature Feature.
* @param {boolean=} opt_listen Whether to listen to the feature change or not
* Defaults to `true`.
* @api
*/
_ol_interaction_Snap_.prototype.addFeature = function(feature, opt_listen) {
var listen = opt_listen !== undefined ? opt_listen : true;
var feature_uid = _ol_.getUid(feature);
var geometry = feature.getGeometry();
if (geometry) {
var segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
if (segmentWriter) {
this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(
_ol_extent_.createEmpty());
segmentWriter.call(this, feature, geometry);
}
}
if (listen) {
this.featureChangeListenerKeys_[feature_uid] = _ol_events_.listen(
feature,
_ol_events_EventType_.CHANGE,
this.handleFeatureChange_, this);
}
};
/**
* @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>} Features.
* @private
*/
_ol_interaction_Snap_.prototype.getFeatures_ = function() {
var features;
if (this.features_) {
features = this.features_;
} else if (this.source_) {
features = this.source_.getFeatures();
}
return /** @type {!Array.<ol.Feature>|!ol.Collection.<ol.Feature>} */ (features);
};
/**
* @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
* @private
*/
_ol_interaction_Snap_.prototype.handleFeatureAdd_ = function(evt) {
var feature;
if (evt instanceof _ol_source_Vector_.Event) {
feature = evt.feature;
} else if (evt instanceof _ol_Collection_.Event) {
feature = evt.element;
}
this.addFeature(/** @type {ol.Feature} */ (feature));
};
/**
* @param {ol.source.Vector.Event|ol.Collection.Event} evt Event.
* @private
*/
_ol_interaction_Snap_.prototype.handleFeatureRemove_ = function(evt) {
var feature;
if (evt instanceof _ol_source_Vector_.Event) {
feature = evt.feature;
} else if (evt instanceof _ol_Collection_.Event) {
feature = evt.element;
}
this.removeFeature(/** @type {ol.Feature} */ (feature));
};
/**
* @param {ol.events.Event} evt Event.
* @private
*/
_ol_interaction_Snap_.prototype.handleFeatureChange_ = function(evt) {
var feature = /** @type {ol.Feature} */ (evt.target);
if (this.handlingDownUpSequence) {
var uid = _ol_.getUid(feature);
if (!(uid in this.pendingFeatures_)) {
this.pendingFeatures_[uid] = feature;
}
} else {
this.updateFeature_(feature);
}
};
/**
* Remove a feature from the collection of features that we may snap to.
* @param {ol.Feature} feature Feature
* @param {boolean=} opt_unlisten Whether to unlisten to the feature change
* or not. Defaults to `true`.
* @api
*/
_ol_interaction_Snap_.prototype.removeFeature = function(feature, opt_unlisten) {
var unlisten = opt_unlisten !== undefined ? opt_unlisten : true;
var feature_uid = _ol_.getUid(feature);
var extent = this.indexedFeaturesExtents_[feature_uid];
if (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) {
_ol_events_.unlistenByKey(this.featureChangeListenerKeys_[feature_uid]);
delete this.featureChangeListenerKeys_[feature_uid];
}
};
/**
* @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_events_.unlistenByKey);
keys.length = 0;
features.forEach(this.forEachFeatureRemove_, this);
}
_ol_interaction_Pointer_.prototype.setMap.call(this, map);
if (map) {
if (this.features_) {
keys.push(
_ol_events_.listen(this.features_, _ol_CollectionEventType_.ADD,
this.handleFeatureAdd_, this),
_ol_events_.listen(this.features_, _ol_CollectionEventType_.REMOVE,
this.handleFeatureRemove_, this)
);
} else if (this.source_) {
keys.push(
_ol_events_.listen(this.source_, _ol_source_VectorEventType_.ADDFEATURE,
this.handleFeatureAdd_, this),
_ol_events_.listen(this.source_, _ol_source_VectorEventType_.REMOVEFEATURE,
this.handleFeatureRemove_, this)
);
}
features.forEach(this.forEachFeatureAdd_, this);
}
};
/**
* @inheritDoc
*/
_ol_interaction_Snap_.prototype.shouldStopEvent = _ol_functions_.FALSE;
/**
* @param {ol.Pixel} pixel Pixel
* @param {ol.Coordinate} pixelCoordinate Coordinate
* @param {ol.PluggableMap} map Map.
* @return {ol.SnapResultType} 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);
// If snapping on vertices only, don't consider circles
if (this.vertex_ && !this.edge_) {
segments = segments.filter(function(segment) {
return segment.feature.getGeometry().getType() !==
_ol_geom_GeometryType_.CIRCLE;
});
}
var snappedToVertex = false;
var snapped = false;
var vertex = null;
var vertexPixel = null;
var dist, pixel1, pixel2, squaredDist1, squaredDist2;
if (segments.length > 0) {
this.pixelCoordinate_ = pixelCoordinate;
segments.sort(this.sortByDistance_);
var closestSegment = segments[0].segment;
var isCircle = segments[0].feature.getGeometry().getType() ===
_ol_geom_GeometryType_.CIRCLE;
if (this.vertex_ && !this.edge_) {
pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
squaredDist1 = _ol_coordinate_.squaredDistance(pixel, pixel1);
squaredDist2 = _ol_coordinate_.squaredDistance(pixel, pixel2);
dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
snappedToVertex = dist <= this.pixelTolerance_;
if (snappedToVertex) {
snapped = true;
vertex = squaredDist1 > squaredDist2 ?
closestSegment[1] : closestSegment[0];
vertexPixel = map.getPixelFromCoordinate(vertex);
}
} else if (this.edge_) {
if (isCircle) {
vertex = _ol_coordinate_.closestOnCircle(pixelCoordinate,
/** @type {ol.geom.Circle} */ (segments[0].feature.getGeometry()));
} else {
vertex = (_ol_coordinate_.closestOnSegment(pixelCoordinate,
closestSegment));
}
vertexPixel = map.getPixelFromCoordinate(vertex);
if (_ol_coordinate_.distance(pixel, vertexPixel) <= this.pixelTolerance_) {
snapped = true;
if (this.vertex_ && !isCircle) {
pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
squaredDist1 = _ol_coordinate_.squaredDistance(vertexPixel, pixel1);
squaredDist2 = _ol_coordinate_.squaredDistance(vertexPixel, pixel2);
dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
snappedToVertex = dist <= this.pixelTolerance_;
if (snappedToVertex) {
vertex = squaredDist1 > squaredDist2 ?
closestSegment[1] : closestSegment[0];
vertexPixel = map.getPixelFromCoordinate(vertex);
}
}
}
}
if (snapped) {
vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
}
}
return /** @type {ol.SnapResultType} */ ({
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.Circle} geometry Geometry.
* @private
*/
_ol_interaction_Snap_.prototype.writeCircleGeometry_ = function(feature, geometry) {
var polygon = _ol_geom_Polygon_.fromCircle(geometry);
var coordinates = polygon.getCoordinates()[0];
var i, ii, segment, segmentData;
for (i = 0, ii = coordinates.length - 1; i < ii; ++i) {
segment = coordinates.slice(i, i + 2);
segmentData = /** @type {ol.SnapSegmentDataType} */ ({
feature: feature,
segment: segment
});
this.rBush_.insert(_ol_extent_.boundingExtent(segment), segmentData);
}
};
/**
* @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) {
var segmentWriter = this.SEGMENT_WRITERS_[geometries[i].getType()];
if (segmentWriter) {
segmentWriter.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.SnapSegmentDataType} */ ({
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.SnapSegmentDataType} */ ({
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.SnapSegmentDataType} */ ({
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.SnapSegmentDataType} */ ({
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.SnapSegmentDataType} */ ({
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.SnapSegmentDataType} */ ({
feature: feature,
segment: segment
});
this.rBush_.insert(_ol_extent_.boundingExtent(segment), segmentData);
}
}
};
/**
* 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.slice(0, 2);
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) {
var featuresToUpdate = _ol_obj_.getValues(this.pendingFeatures_);
if (featuresToUpdate.length) {
featuresToUpdate.forEach(this.updateFeature_, this);
this.pendingFeatures_ = {};
}
return false;
};
/**
* Sort segments by distance, helper function
* @param {ol.SnapSegmentDataType} a The first segment data.
* @param {ol.SnapSegmentDataType} b The second segment data.
* @return {number} The difference in distance.
* @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);
};
export default _ol_interaction_Snap_;