User coordinates during snapping
This commit is contained in:
@@ -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: {
|
||||||
|
|||||||
@@ -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
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user