User coordinates during snapping

This commit is contained in:
Tim Schaub
2019-09-27 11:55:48 +02:00
parent b40709ea5b
commit 07678d960a
3 changed files with 109 additions and 97 deletions
+15 -4
View File
@@ -1,6 +1,6 @@
import {Map, View} from '../src/ol/index.js'; import {Map, View} from '../src/ol/index.js';
import GeoJSON from '../src/ol/format/GeoJSON.js'; import GeoJSON from '../src/ol/format/GeoJSON.js';
import {Modify, Select, Draw} from '../src/ol/interaction.js'; import {Modify, Select, Draw, Snap} from '../src/ol/interaction.js';
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js'; import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
import {OSM, Vector as VectorSource} from '../src/ol/source.js'; import {OSM, Vector as VectorSource} from '../src/ol/source.js';
import {useGeographic} from '../src/ol/proj.js'; import {useGeographic} from '../src/ol/proj.js';
@@ -39,19 +39,30 @@ const draw = new Draw({
source: source source: source
}); });
const snap = new Snap({
source: source
});
function removeInteractions() {
map.removeInteraction(modify);
map.removeInteraction(select);
map.removeInteraction(draw);
map.removeInteraction(select);
}
const mode = document.getElementById('mode'); const mode = document.getElementById('mode');
function onChange() { function onChange() {
removeInteractions();
switch (mode.value) { switch (mode.value) {
case 'draw': { case 'draw': {
map.removeInteraction(modify);
map.removeInteraction(select);
map.addInteraction(draw); map.addInteraction(draw);
map.addInteraction(snap);
break; break;
} }
case 'modify': { case 'modify': {
map.removeInteraction(draw);
map.addInteraction(select); map.addInteraction(select);
map.addInteraction(modify); map.addInteraction(modify);
map.addInteraction(snap);
break; break;
} }
default: { default: {
+10 -5
View File
@@ -920,12 +920,12 @@ class Modify extends PointerInteraction {
*/ */
handlePointerAtPixel_(pixel, map) { handlePointerAtPixel_(pixel, map) {
const pixelCoordinate = map.getCoordinateFromPixel(pixel); const pixelCoordinate = map.getCoordinateFromPixel(pixel);
const projection = map.getView().getProjection();
const sortByDistance = function(a, b) { const sortByDistance = function(a, b) {
return pointDistanceToSegmentDataSquared(pixelCoordinate, a) - return projectedDistanceToSegmentDataSquared(pixelCoordinate, a, projection) -
pointDistanceToSegmentDataSquared(pixelCoordinate, b); projectedDistanceToSegmentDataSquared(pixelCoordinate, b, projection);
}; };
const projection = map.getView().getProjection();
const viewExtent = fromUserExtent(createExtent(pixelCoordinate, tempExtent), projection); const viewExtent = fromUserExtent(createExtent(pixelCoordinate, tempExtent), projection);
const buffer = map.getView().getResolution() * this.pixelTolerance_; const buffer = map.getView().getResolution() * this.pixelTolerance_;
const box = toUserExtent(bufferExtent(viewExtent, buffer, tempExtent), projection); const box = toUserExtent(bufferExtent(viewExtent, buffer, tempExtent), projection);
@@ -1235,9 +1235,10 @@ function compareIndexes(a, b) {
* which to calculate the distance. * which to calculate the distance.
* @param {SegmentData} segmentData The object describing the line * @param {SegmentData} segmentData The object describing the line
* segment we are calculating the distance to. * segment we are calculating the distance to.
* @param {import("../proj/Projection.js").default} projection The view projection.
* @return {number} The square of the distance between a point and a line segment. * @return {number} The square of the distance between a point and a line segment.
*/ */
function pointDistanceToSegmentDataSquared(pointCoordinates, segmentData) { function projectedDistanceToSegmentDataSquared(pointCoordinates, segmentData, projection) {
const geometry = segmentData.geometry; const geometry = segmentData.geometry;
if (geometry.getType() === GeometryType.CIRCLE) { if (geometry.getType() === GeometryType.CIRCLE) {
@@ -1251,7 +1252,11 @@ function pointDistanceToSegmentDataSquared(pointCoordinates, segmentData) {
return distanceToCircumference * distanceToCircumference; return distanceToCircumference * distanceToCircumference;
} }
} }
return squaredDistanceToSegment(pointCoordinates, segmentData.segment);
const coordinate = fromUserCoordinate(pointCoordinates, projection);
tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection);
tempSegment[1] = fromUserCoordinate(segmentData.segment[1], projection);
return squaredDistanceToSegment(coordinate, tempSegment);
} }
/** /**
+84 -88
View File
@@ -14,6 +14,7 @@ import PointerInteraction from './Pointer.js';
import {getValues} from '../obj.js'; import {getValues} from '../obj.js';
import VectorEventType from '../source/VectorEventType.js'; import VectorEventType from '../source/VectorEventType.js';
import RBush from '../structs/RBush.js'; import RBush from '../structs/RBush.js';
import {fromUserCoordinate, toUserCoordinate} from '../proj.js';
/** /**
@@ -52,9 +53,10 @@ function getFeatureFromEvent(evt) {
} else if (/** @type {import("../Collection.js").CollectionEvent} */ (evt).element) { } else if (/** @type {import("../Collection.js").CollectionEvent} */ (evt).element) {
return /** @type {import("../Feature.js").default} */ (/** @type {import("../Collection.js").CollectionEvent} */ (evt).element); return /** @type {import("../Feature.js").default} */ (/** @type {import("../Collection.js").CollectionEvent} */ (evt).element);
} }
} }
const tempSegment = [];
/** /**
* @classdesc * @classdesc
* Handles snapping of vector features while modifying or drawing them. The * Handles snapping of vector features while modifying or drawing them. The
@@ -70,10 +72,12 @@ function getFeatureFromEvent(evt) {
* *
* import Snap from 'ol/interaction/Snap'; * import Snap from 'ol/interaction/Snap';
* *
* var snap = new Snap({ * const snap = new Snap({
* source: source * source: source
* }); * });
* *
* map.addInteraction(snap);
*
* @api * @api
*/ */
class Snap extends PointerInteraction { class Snap extends PointerInteraction {
@@ -149,13 +153,6 @@ class Snap extends PointerInteraction {
*/ */
this.pendingFeatures_ = {}; this.pendingFeatures_ = {};
/**
* Used for distance sorting in sortByDistance_
* @type {import("../coordinate.js").Coordinate}
* @private
*/
this.pixelCoordinate_ = null;
/** /**
* @type {number} * @type {number}
* @private * @private
@@ -163,13 +160,6 @@ class Snap extends PointerInteraction {
this.pixelTolerance_ = options.pixelTolerance !== undefined ? this.pixelTolerance_ = options.pixelTolerance !== undefined ?
options.pixelTolerance : 10; options.pixelTolerance : 10;
/**
* @type {function(SegmentData, SegmentData): number}
* @private
*/
this.sortByDistance_ = sortByDistance.bind(this);
/** /**
* Segment RTree for each layer * Segment RTree for each layer
* @type {import("../structs/RBush.js").default<SegmentData>} * @type {import("../structs/RBush.js").default<SegmentData>}
@@ -177,22 +167,21 @@ class Snap extends PointerInteraction {
*/ */
this.rBush_ = new RBush(); this.rBush_ = new RBush();
/** /**
* @const * @const
* @private * @private
* @type {Object<string, function(import("../Feature.js").default, import("../geom/Geometry.js").default): void>} * @type {Object<string, function(import("../Feature.js").default, import("../geom/Geometry.js").default): void>}
*/ */
this.SEGMENT_WRITERS_ = { this.SEGMENT_WRITERS_ = {
'Point': this.writePointGeometry_, 'Point': this.writePointGeometry_.bind(this),
'LineString': this.writeLineStringGeometry_, 'LineString': this.writeLineStringGeometry_.bind(this),
'LinearRing': this.writeLineStringGeometry_, 'LinearRing': this.writeLineStringGeometry_.bind(this),
'Polygon': this.writePolygonGeometry_, 'Polygon': this.writePolygonGeometry_.bind(this),
'MultiPoint': this.writeMultiPointGeometry_, 'MultiPoint': this.writeMultiPointGeometry_.bind(this),
'MultiLineString': this.writeMultiLineStringGeometry_, 'MultiLineString': this.writeMultiLineStringGeometry_.bind(this),
'MultiPolygon': this.writeMultiPolygonGeometry_, 'MultiPolygon': this.writeMultiPolygonGeometry_.bind(this),
'GeometryCollection': this.writeGeometryCollectionGeometry_, 'GeometryCollection': this.writeGeometryCollectionGeometry_.bind(this),
'Circle': this.writeCircleGeometry_ 'Circle': this.writeCircleGeometry_.bind(this)
}; };
} }
@@ -211,7 +200,7 @@ class Snap extends PointerInteraction {
const segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()]; const segmentWriter = this.SEGMENT_WRITERS_[geometry.getType()];
if (segmentWriter) { if (segmentWriter) {
this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(createEmpty()); this.indexedFeaturesExtents_[feature_uid] = geometry.getExtent(createEmpty());
segmentWriter.call(this, feature, geometry); segmentWriter(feature, geometry);
} }
} }
@@ -383,10 +372,9 @@ class Snap extends PointerInteraction {
* @return {Result} Snap result * @return {Result} Snap result
*/ */
snapTo(pixel, pixelCoordinate, map) { snapTo(pixel, pixelCoordinate, map) {
const lowerLeft = map.getCoordinateFromPixel(
const lowerLeft = map.getCoordinateFromPixelInternal(
[pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]); [pixel[0] - this.pixelTolerance_, pixel[1] + this.pixelTolerance_]);
const upperRight = map.getCoordinateFromPixelInternal( const upperRight = map.getCoordinateFromPixel(
[pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]); [pixel[0] + this.pixelTolerance_, pixel[1] - this.pixelTolerance_]);
const box = boundingExtent([lowerLeft, upperRight]); const box = boundingExtent([lowerLeft, upperRight]);
@@ -400,57 +388,78 @@ class Snap extends PointerInteraction {
}); });
} }
let snappedToVertex = false;
let snapped = false; let snapped = false;
let vertex = null; let vertex = null;
let vertexPixel = null; let vertexPixel = null;
let dist, pixel1, pixel2, squaredDist1, squaredDist2;
if (segments.length > 0) { if (segments.length === 0) {
this.pixelCoordinate_ = pixelCoordinate; return {
segments.sort(this.sortByDistance_); snapped: snapped,
const closestSegment = segments[0].segment; vertex: vertex,
const isCircle = segments[0].feature.getGeometry().getType() === vertexPixel: vertexPixel
GeometryType.CIRCLE; };
if (this.vertex_ && !this.edge_) { }
pixel1 = map.getPixelFromCoordinateInternal(closestSegment[0]);
pixel2 = map.getPixelFromCoordinateInternal(closestSegment[1]); const projection = map.getView().getProjection();
squaredDist1 = squaredCoordinateDistance(pixel, pixel1); const projectedCoordinate = fromUserCoordinate(pixelCoordinate, projection);
squaredDist2 = squaredCoordinateDistance(pixel, pixel2);
dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); let closestSegmentData;
snappedToVertex = dist <= this.pixelTolerance_; let minSquaredDistance = Infinity;
if (snappedToVertex) { for (let i = 0; i < segments.length; ++i) {
snapped = true; const segmentData = segments[i];
vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0]; tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection);
vertexPixel = map.getPixelFromCoordinateInternal(vertex); tempSegment[1] = fromUserCoordinate(segmentData.segment[1], projection);
} const delta = squaredDistanceToSegment(projectedCoordinate, tempSegment);
} else if (this.edge_) { if (delta < minSquaredDistance) {
if (isCircle) { closestSegmentData = segmentData;
vertex = closestOnCircle(pixelCoordinate, minSquaredDistance = delta;
/** @type {import("../geom/Circle.js").default} */ (segments[0].feature.getGeometry())); }
} else { }
vertex = closestOnSegment(pixelCoordinate, closestSegment); const closestSegment = closestSegmentData.segment;
}
vertexPixel = map.getPixelFromCoordinateInternal(vertex); if (this.vertex_ && !this.edge_) {
if (coordinateDistance(pixel, vertexPixel) <= this.pixelTolerance_) { const pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
snapped = true; const pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
if (this.vertex_ && !isCircle) { const squaredDist1 = squaredCoordinateDistance(pixel, pixel1);
pixel1 = map.getPixelFromCoordinateInternal(closestSegment[0]); const squaredDist2 = squaredCoordinateDistance(pixel, pixel2);
pixel2 = map.getPixelFromCoordinateInternal(closestSegment[1]); const dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
squaredDist1 = squaredCoordinateDistance(vertexPixel, pixel1); if (dist <= this.pixelTolerance_) {
squaredDist2 = squaredCoordinateDistance(vertexPixel, pixel2); snapped = true;
dist = Math.sqrt(Math.min(squaredDist1, squaredDist2)); vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0];
snappedToVertex = dist <= this.pixelTolerance_; vertexPixel = map.getPixelFromCoordinate(vertex);
if (snappedToVertex) { }
vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0]; } else if (this.edge_) {
vertexPixel = map.getPixelFromCoordinateInternal(vertex); const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE;
} if (isCircle) {
vertex = closestOnCircle(pixelCoordinate,
/** @type {import("../geom/Circle.js").default} */ (closestSegmentData.feature.getGeometry()));
} else {
tempSegment[0] = fromUserCoordinate(closestSegment[0], projection);
tempSegment[1] = fromUserCoordinate(closestSegment[1], projection);
vertex = toUserCoordinate(closestOnSegment(projectedCoordinate, tempSegment), projection);
}
vertexPixel = map.getPixelFromCoordinate(vertex);
if (coordinateDistance(pixel, vertexPixel) <= this.pixelTolerance_) {
snapped = true;
if (this.vertex_ && !isCircle) {
const pixel1 = map.getPixelFromCoordinate(closestSegment[0]);
const pixel2 = map.getPixelFromCoordinate(closestSegment[1]);
const squaredDist1 = squaredCoordinateDistance(vertexPixel, pixel1);
const squaredDist2 = squaredCoordinateDistance(vertexPixel, pixel2);
const dist = Math.sqrt(Math.min(squaredDist1, squaredDist2));
if (dist <= this.pixelTolerance_) {
vertex = squaredDist1 > squaredDist2 ? closestSegment[1] : closestSegment[0];
vertexPixel = map.getPixelFromCoordinate(vertex);
} }
} }
} }
if (snapped) {
vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
}
} }
if (snapped) {
vertexPixel = [Math.round(vertexPixel[0]), Math.round(vertexPixel[1])];
}
return { return {
snapped: snapped, snapped: snapped,
vertex: vertex, vertex: vertex,
@@ -495,7 +504,7 @@ class Snap extends PointerInteraction {
for (let i = 0; i < geometries.length; ++i) { for (let i = 0; i < geometries.length; ++i) {
const segmentWriter = this.SEGMENT_WRITERS_[geometries[i].getType()]; const segmentWriter = this.SEGMENT_WRITERS_[geometries[i].getType()];
if (segmentWriter) { if (segmentWriter) {
segmentWriter.call(this, feature, geometries[i]); segmentWriter(feature, geometries[i]);
} }
} }
} }
@@ -613,17 +622,4 @@ class Snap extends PointerInteraction {
} }
/**
* Sort segments by distance, helper function
* @param {SegmentData} a The first segment data.
* @param {SegmentData} b The second segment data.
* @return {number} The difference in distance.
* @this {Snap}
*/
function sortByDistance(a, b) {
const deltaA = squaredDistanceToSegment(this.pixelCoordinate_, a.segment);
const deltaB = squaredDistanceToSegment(this.pixelCoordinate_, b.segment);
return deltaA - deltaB;
}
export default Snap; export default Snap;