diff --git a/src/ol/interaction/Modify.js b/src/ol/interaction/Modify.js index fe81a771ee..dffdf8e707 100644 --- a/src/ol/interaction/Modify.js +++ b/src/ol/interaction/Modify.js @@ -14,13 +14,14 @@ import {always, primaryAction, altKeyOnly, singleClick} from '../events/conditio import {boundingExtent, buffer as bufferExtent, createOrUpdateFromCoordinate as createExtent} from '../extent.js'; import GeometryType from '../geom/GeometryType.js'; import Point from '../geom/Point.js'; +import {fromCircle} from '../geom/Polygon.js'; import PointerInteraction from './Pointer.js'; import VectorLayer from '../layer/Vector.js'; import VectorSource from '../source/Vector.js'; import VectorEventType from '../source/VectorEventType.js'; import RBush from '../structs/RBush.js'; import {createEditingStyle} from '../style/Style.js'; -import {fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js'; +import {getUserProjection, fromUserExtent, toUserExtent, fromUserCoordinate, toUserCoordinate} from '../proj.js'; /** @@ -657,7 +658,14 @@ class Modify extends PointerInteraction { centerSegmentData.featureSegments = featureSegments; circumferenceSegmentData.featureSegments = featureSegments; this.rBush_.insert(createExtent(coordinates), centerSegmentData); - this.rBush_.insert(geometry.getExtent(), circumferenceSegmentData); + let circleGeometry = /** @type {import("../geom/Geometry.js").default} */ (geometry); + const userProjection = getUserProjection(); + if (userProjection && this.getMap()) { + const projection = this.getMap().getView().getProjection(); + circleGeometry = circleGeometry.clone().transform(userProjection, projection); + circleGeometry = fromCircle(/** @type {import("../geom/Circle.js").default} */ (circleGeometry)).transform(projection, userProjection); + } + this.rBush_.insert(circleGeometry.getExtent(), circumferenceSegmentData); } /** @@ -785,7 +793,16 @@ class Modify extends PointerInteraction { this.changingFeature_ = false; } else { // We're dragging the circle's circumference: this.changingFeature_ = true; - geometry.setRadius(coordinateDistance(geometry.getCenter(), vertex)); + const projection = evt.map.getView().getProjection(); + let radius = coordinateDistance(fromUserCoordinate(geometry.getCenter(), projection), + fromUserCoordinate(vertex, projection)); + const userProjection = getUserProjection(); + if (userProjection) { + const circleGeometry = geometry.clone().transform(userProjection, projection); + circleGeometry.setRadius(radius); + radius = circleGeometry.transform(projection, userProjection).getRadius(); + } + geometry.setRadius(radius); this.changingFeature_ = false; } break; @@ -898,7 +915,14 @@ class Modify extends PointerInteraction { circumferenceSegmentData.segment[0] = coordinates; circumferenceSegmentData.segment[1] = coordinates; this.rBush_.update(createExtent(coordinates), centerSegmentData); - this.rBush_.update(geometry.getExtent(), circumferenceSegmentData); + let circleGeometry = geometry; + const userProjection = getUserProjection(); + if (userProjection) { + const projection = evt.map.getView().getProjection(); + circleGeometry = circleGeometry.clone().transform(userProjection, projection); + circleGeometry = fromCircle(circleGeometry).transform(projection, userProjection); + } + this.rBush_.update(circleGeometry.getExtent(), circumferenceSegmentData); } else { this.rBush_.update(boundingExtent(segmentData.segment), segmentData); } @@ -1249,11 +1273,15 @@ function projectedDistanceToSegmentDataSquared(pointCoordinates, segmentData, pr const geometry = segmentData.geometry; if (geometry.getType() === GeometryType.CIRCLE) { - const circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry); + let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry); if (segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { + const userProjection = getUserProjection(); + if (userProjection) { + circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection)); + } const distanceToCenterSquared = - squaredCoordinateDistance(circleGeometry.getCenter(), pointCoordinates); + squaredCoordinateDistance(circleGeometry.getCenter(), fromUserCoordinate(pointCoordinates, projection)); const distanceToCircumference = Math.sqrt(distanceToCenterSquared) - circleGeometry.getRadius(); return distanceToCircumference * distanceToCircumference; @@ -1280,7 +1308,12 @@ function closestOnSegmentData(pointCoordinates, segmentData, projection) { const geometry = segmentData.geometry; if (geometry.getType() === GeometryType.CIRCLE && segmentData.index === CIRCLE_CIRCUMFERENCE_INDEX) { - return geometry.getClosestPoint(pointCoordinates); + let circleGeometry = /** @type {import("../geom/Circle.js").default} */ (geometry); + const userProjection = getUserProjection(); + if (userProjection) { + circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection)); + } + return toUserCoordinate(circleGeometry.getClosestPoint(fromUserCoordinate(pointCoordinates, projection)), projection); } const coordinate = fromUserCoordinate(pointCoordinates, projection); tempSegment[0] = fromUserCoordinate(segmentData.segment[0], projection); diff --git a/src/ol/interaction/Snap.js b/src/ol/interaction/Snap.js index 8cf720768a..61a5a1c6de 100644 --- a/src/ol/interaction/Snap.js +++ b/src/ol/interaction/Snap.js @@ -14,7 +14,7 @@ import PointerInteraction from './Pointer.js'; import {getValues} from '../obj.js'; import VectorEventType from '../source/VectorEventType.js'; import RBush from '../structs/RBush.js'; -import {fromUserCoordinate, toUserCoordinate} from '../proj.js'; +import {getUserProjection, fromUserCoordinate, toUserCoordinate} from '../proj.js'; /** @@ -431,8 +431,13 @@ class Snap extends PointerInteraction { } else if (this.edge_) { const isCircle = closestSegmentData.feature.getGeometry().getType() === GeometryType.CIRCLE; if (isCircle) { - vertex = closestOnCircle(pixelCoordinate, - /** @type {import("../geom/Circle.js").default} */ (closestSegmentData.feature.getGeometry())); + let circleGeometry = closestSegmentData.feature.getGeometry(); + const userProjection = getUserProjection(); + if (userProjection) { + circleGeometry = circleGeometry.clone().transform(userProjection, projection); + } + vertex = toUserCoordinate(closestOnCircle(projectedCoordinate, + /** @type {import("../geom/Circle.js").default} */ (circleGeometry)), projection); } else { tempSegment[0] = fromUserCoordinate(closestSegment[0], projection); tempSegment[1] = fromUserCoordinate(closestSegment[1], projection); @@ -482,7 +487,16 @@ class Snap extends PointerInteraction { * @private */ writeCircleGeometry_(feature, geometry) { - const polygon = fromCircle(geometry); + const projection = this.getMap().getView().getProjection(); + let circleGeometry = geometry; + const userProjection = getUserProjection(); + if (userProjection) { + circleGeometry = /** @type {import("../geom/Circle.js").default} */ (circleGeometry.clone().transform(userProjection, projection)); + } + const polygon = fromCircle(circleGeometry); + if (userProjection) { + polygon.transform(projection, userProjection); + } const coordinates = polygon.getCoordinates()[0]; for (let i = 0, ii = coordinates.length - 1; i < ii; ++i) { const segment = coordinates.slice(i, i + 2); diff --git a/test/spec/ol/interaction/modify.test.js b/test/spec/ol/interaction/modify.test.js index 18b2c28063..733e965a02 100644 --- a/test/spec/ol/interaction/modify.test.js +++ b/test/spec/ol/interaction/modify.test.js @@ -14,6 +14,7 @@ import VectorLayer from '../../../../src/ol/layer/Vector.js'; import VectorSource from '../../../../src/ol/source/Vector.js'; import Event from '../../../../src/ol/events/Event.js'; import {getValues} from '../../../../src/ol/obj.js'; +import {clearUserProjection, setUserProjection} from '../../../../src/ol/proj.js'; describe('ol.interaction.Modify', function() { @@ -66,6 +67,7 @@ describe('ol.interaction.Modify', function() { afterEach(function() { map.dispose(); document.body.removeChild(target); + clearUserProjection(); }); /** @@ -402,7 +404,7 @@ describe('ol.interaction.Modify', function() { expect(circleFeature.getGeometry().getRadius()).to.equal(20); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); - // Increase radius + // Increase radius along x axis simulateEvent('pointermove', 25, -4, null, 0); simulateEvent('pointerdown', 25, -4, null, 0); simulateEvent('pointermove', 30, -5, null, 0); @@ -411,6 +413,64 @@ describe('ol.interaction.Modify', function() { expect(circleFeature.getGeometry().getRadius()).to.equal(25); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); + + // Increase radius along y axis + simulateEvent('pointermove', 4, -30, null, 0); + simulateEvent('pointerdown', 4, -30, null, 0); + simulateEvent('pointermove', 5, -35, null, 0); + simulateEvent('pointerdrag', 5, -35, null, 0); + simulateEvent('pointerup', 5, -35, null, 0); + + expect(circleFeature.getGeometry().getRadius()).to.equal(30); + expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); + }); + + it('changes the circle radius and center in a user projection', function() { + const userProjection = 'EPSG:3857'; + setUserProjection(userProjection); + const viewProjection = map.getView().getProjection(); + + const circleFeature = new Feature(new Circle([10, 10], 20).transform(viewProjection, userProjection)); + features.length = 0; + features.push(circleFeature); + + const modify = new Modify({ + features: new Collection(features) + }); + map.addInteraction(modify); + + // Change center + simulateEvent('pointermove', 10, -10, null, 0); + simulateEvent('pointerdown', 10, -10, null, 0); + simulateEvent('pointermove', 5, -5, null, 0); + simulateEvent('pointerdrag', 5, -5, null, 0); + simulateEvent('pointerup', 5, -5, null, 0); + + const geometry1 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry1.getRadius()).to.roughlyEqual(20, 1e-9); + expect(geometry1.getCenter()).to.eql([5, 5]); + + // Increase radius along x axis + simulateEvent('pointermove', 25, -4, null, 0); + simulateEvent('pointerdown', 25, -4, null, 0); + simulateEvent('pointermove', 30, -5, null, 0); + simulateEvent('pointerdrag', 30, -5, null, 0); + simulateEvent('pointerup', 30, -5, null, 0); + + const geometry2 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry2.getRadius()).to.roughlyEqual(25, 1e-9); + expect(geometry2.getCenter()).to.eql([5, 5]); + + // Increase radius along y axis + simulateEvent('pointermove', 4, -30, null, 0); + simulateEvent('pointerdown', 4, -30, null, 0); + simulateEvent('pointermove', 5, -35, null, 0); + simulateEvent('pointerdrag', 5, -35, null, 0); + simulateEvent('pointerup', 5, -35, null, 0); + + const geometry3 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry3.getRadius()).to.roughlyEqual(30, 1e-9); + expect(geometry3.getCenter()).to.eql([5, 5]); }); }); @@ -766,7 +826,7 @@ describe('ol.interaction.Modify', function() { expect(circleFeature.getGeometry().getRadius()).to.equal(20); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); - // Increase radius + // Increase radius along x axis simulateEvent('pointermove', 25, -4, null, 0); simulateEvent('pointerdown', 25, -4, null, 0); simulateEvent('pointermove', 30, -5, null, 0); @@ -775,6 +835,70 @@ describe('ol.interaction.Modify', function() { expect(circleFeature.getGeometry().getRadius()).to.equal(25); expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); + + // Increase radius along y axis + simulateEvent('pointermove', 4, -30, null, 0); + simulateEvent('pointerdown', 4, -30, null, 0); + simulateEvent('pointermove', 5, -35, null, 0); + simulateEvent('pointerdrag', 5, -35, null, 0); + simulateEvent('pointerup', 5, -35, null, 0); + + expect(circleFeature.getGeometry().getRadius()).to.equal(30); + expect(circleFeature.getGeometry().getCenter()).to.eql([5, 5]); + }); + + it('changes the circle radius and center in a user projection', function() { + const userProjection = 'EPSG:3857'; + setUserProjection(userProjection); + const viewProjection = map.getView().getProjection(); + + const circleFeature = new Feature(new Circle([10, 10], 20).transform(viewProjection, userProjection)); + features.length = 0; + features.push(circleFeature); + + const modify = new Modify({ + features: new Collection(features) + }); + map.addInteraction(modify); + + const snap = new Snap({ + features: new Collection(features), + pixelTolerance: 1 + }); + map.addInteraction(snap); + + // Change center + simulateEvent('pointermove', 10, -10, null, 0); + simulateEvent('pointerdown', 10, -10, null, 0); + simulateEvent('pointermove', 5, -5, null, 0); + simulateEvent('pointerdrag', 5, -5, null, 0); + simulateEvent('pointerup', 5, -5, null, 0); + + const geometry1 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry1.getRadius()).to.roughlyEqual(20, 1e-9); + expect(geometry1.getCenter()).to.eql([5, 5]); + + // Increase radius along x axis + simulateEvent('pointermove', 25, -4, null, 0); + simulateEvent('pointerdown', 25, -4, null, 0); + simulateEvent('pointermove', 30, -5, null, 0); + simulateEvent('pointerdrag', 30, -5, null, 0); + simulateEvent('pointerup', 30, -5, null, 0); + + const geometry2 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry2.getRadius()).to.roughlyEqual(25, 1e-9); + expect(geometry2.getCenter()).to.eql([5, 5]); + + // Increase radius along y axis + simulateEvent('pointermove', 4, -30, null, 0); + simulateEvent('pointerdown', 4, -30, null, 0); + simulateEvent('pointermove', 5, -35, null, 0); + simulateEvent('pointerdrag', 5, -35, null, 0); + simulateEvent('pointerup', 5, -35, null, 0); + + const geometry3 = circleFeature.getGeometry().clone().transform(userProjection, viewProjection); + expect(geometry3.getRadius()).to.roughlyEqual(30, 1e-9); + expect(geometry3.getCenter()).to.eql([5, 5]); }); }); diff --git a/test/spec/ol/interaction/snap.test.js b/test/spec/ol/interaction/snap.test.js index 329cd08172..7db59f177b 100644 --- a/test/spec/ol/interaction/snap.test.js +++ b/test/spec/ol/interaction/snap.test.js @@ -6,7 +6,7 @@ import Circle from '../../../../src/ol/geom/Circle.js'; import Point from '../../../../src/ol/geom/Point.js'; import LineString from '../../../../src/ol/geom/LineString.js'; import Snap from '../../../../src/ol/interaction/Snap.js'; -import {useGeographic, clearUserProjection} from '../../../../src/ol/proj.js'; +import {useGeographic, clearUserProjection, setUserProjection, transform} from '../../../../src/ol/proj.js'; import {overrideRAF} from '../../util.js'; @@ -55,6 +55,7 @@ describe('ol.interaction.Snap', function() { afterEach(function() { map.dispose(); document.body.removeChild(target); + clearUserProjection(); }); it('can handle XYZ coordinates', function() { @@ -129,6 +130,30 @@ describe('ol.interaction.Snap', function() { expect(event.coordinate[1]).to.roughlyEqual(Math.sin(Math.PI / 4) * 10, 1e-10); }); + it('snaps to circle in a user projection', function() { + const userProjection = 'EPSG:3857'; + setUserProjection(userProjection); + const viewProjection = map.getView().getProjection(); + + const circle = new Feature(new Circle([0, 0], 10).transform(viewProjection, userProjection)); + const snapInteraction = new Snap({ + features: new Collection([circle]), + pixelTolerance: 5 + }); + snapInteraction.setMap(map); + + const event = { + pixel: [5 + width / 2, height / 2 - 5], + coordinate: transform([5, 5], viewProjection, userProjection), + map: map + }; + snapInteraction.handleEvent(event); + + const coordinate = transform([Math.sin(Math.PI / 4) * 10, Math.sin(Math.PI / 4) * 10], viewProjection, userProjection); + expect(event.coordinate[0]).to.roughlyEqual(coordinate[0], 1e-10); + expect(event.coordinate[1]).to.roughlyEqual(coordinate[1], 1e-10); + }); + it('handle feature without geometry', function() { const feature = new Feature(); const snapInteraction = new Snap({