From 1e27846d6d0115a148d6c8769d3a1c3af6b647e4 Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 26 Sep 2019 11:23:55 +0200 Subject: [PATCH 1/2] Use user projection for event coordinate --- src/ol/MapBrowserEvent.js | 7 +- test/spec/ol/MapBrowserEvent.test.js | 143 ++++++++++++++++++ ...test.js => MapBrowserEventHandler.test.js} | 2 +- 3 files changed, 148 insertions(+), 4 deletions(-) create mode 100644 test/spec/ol/MapBrowserEvent.test.js rename test/spec/ol/{mapbrowserevent.test.js => MapBrowserEventHandler.test.js} (99%) diff --git a/src/ol/MapBrowserEvent.js b/src/ol/MapBrowserEvent.js index ce60448307..956c639f24 100644 --- a/src/ol/MapBrowserEvent.js +++ b/src/ol/MapBrowserEvent.js @@ -36,7 +36,7 @@ class MapBrowserEvent extends MapEvent { this.pixel_ = null; /** - * The coordinate in view projection corresponding to the original browser event. + * The coordinate in the user projection corresponding to the original browser event. * @type {import("./coordinate.js").Coordinate} */ this.coordinate_ = null; @@ -68,13 +68,14 @@ class MapBrowserEvent extends MapEvent { } /** - * The coordinate in view projection corresponding to the original browser event. + * The coordinate corresponding to the original browser event. This will be in the user + * projection if one is set. Otherwise it will be in the view projection. * @type {import("./coordinate.js").Coordinate} * @api */ get coordinate() { if (!this.coordinate_) { - this.coordinate_ = this.map.getCoordinateFromPixelInternal(this.pixel); + this.coordinate_ = this.map.getCoordinateFromPixel(this.pixel); } return this.coordinate_; } diff --git a/test/spec/ol/MapBrowserEvent.test.js b/test/spec/ol/MapBrowserEvent.test.js new file mode 100644 index 0000000000..d7093d1f90 --- /dev/null +++ b/test/spec/ol/MapBrowserEvent.test.js @@ -0,0 +1,143 @@ +import {Map, View} from '../../../src/ol/index.js'; +import MapBrowserEvent from '../../../src/ol/MapBrowserEvent.js'; +import Event from '../../../src/ol/events/Event.js'; +import {useGeographic, clearUserProjection} from '../../../src/ol/proj.js'; + +function createMap() { + const size = 256; + const target = document.createElement('div'); + Object.assign(target.style, { + width: `${size}px`, + height: `${size}px`, + position: 'absolute', + top: 0, + left: 0 + }); + document.body.appendChild(target); + + const map = new Map({ + target: target, + view: new View({ + center: [0, 0], + zoom: 0 + }) + }); + return {map, target, size}; +} + + +describe('ol/MapBrowserEvent', function() { + + describe('pixel', function() { + let ref; + beforeEach(() => { + ref = createMap(); + }); + + afterEach(() => { + ref.map.dispose(); + document.body.removeChild(ref.target); + }); + + it('is the pixel position of the event', () => { + const x = 10; + const y = 15; + + const event = new Event(); + event.clientX = x; + event.clientY = y; + const mapEvent = new MapBrowserEvent('test', ref.map, event); + + expect(mapEvent.pixel).to.eql([x, y]); + }); + + it('is settable', () => { + const x = 10; + const y = 15; + + const event = new Event(); + event.clientX = x; + event.clientY = y; + const mapEvent = new MapBrowserEvent('test', ref.map, event); + + expect(mapEvent.pixel).to.eql([x, y]); + + const pixel = [x + 5, y + 5]; + mapEvent.pixel = pixel; + expect(mapEvent.pixel).to.eql(pixel); + }); + + }); + + describe('coordinate', function() { + let ref; + beforeEach(() => { + ref = createMap(); + ref.map.renderSync(); + }); + + afterEach(() => { + ref.map.dispose(); + document.body.removeChild(ref.target); + }); + + it('is the map coordinate of the event', () => { + const x = ref.size / 2; + const y = ref.size / 2; + + const event = new Event(); + event.clientX = x; + event.clientY = y; + const mapEvent = new MapBrowserEvent('test', ref.map, event); + + expect(mapEvent.coordinate).to.eql([0, 0]); + }); + + it('is settable', () => { + const x = ref.size / 2; + const y = ref.size / 2; + + const event = new Event(); + event.clientX = x; + event.clientY = y; + const mapEvent = new MapBrowserEvent('test', ref.map, event); + + expect(mapEvent.coordinate).to.eql([0, 0]); + + const coordinate = [1, 2]; + mapEvent.coordinate = coordinate; + expect(mapEvent.coordinate).to.eql(coordinate); + }); + + }); + + describe('coordinate - with useGeographic()', function() { + let ref; + beforeEach(() => { + useGeographic(); + ref = createMap(); + ref.map.renderSync(); + }); + + afterEach(() => { + clearUserProjection(); + ref.map.dispose(); + document.body.removeChild(ref.target); + }); + + it('is the geographic coordinate of the event', () => { + const x = ref.size / 4; + const y = ref.size / 4; + + const event = new Event(); + event.clientX = x; + event.clientY = y; + const mapEvent = new MapBrowserEvent('test', ref.map, event); + + const coord = mapEvent.coordinate; + expect(coord[0]).to.be(-90); + expect(coord[1]).to.roughlyEqual(66.5132, 1e-4); + }); + + }); +}); diff --git a/test/spec/ol/mapbrowserevent.test.js b/test/spec/ol/MapBrowserEventHandler.test.js similarity index 99% rename from test/spec/ol/mapbrowserevent.test.js rename to test/spec/ol/MapBrowserEventHandler.test.js index 846b457282..15d6db9586 100644 --- a/test/spec/ol/mapbrowserevent.test.js +++ b/test/spec/ol/MapBrowserEventHandler.test.js @@ -4,7 +4,7 @@ import {listen} from '../../../src/ol/events.js'; import {DEVICE_PIXEL_RATIO} from '../../../src/ol/has.js'; import Event from '../../../src/ol/events/Event.js'; -describe('ol.MapBrowserEventHandler', function() { +describe('ol/MapBrowserEventHandler', function() { describe('#emulateClick_', function() { let clock; let handler; From 5d4e77151c3eb2c8cd509969a291f4d6c9e3402c Mon Sep 17 00:00:00 2001 From: Tim Schaub Date: Thu, 26 Sep 2019 15:21:26 +0200 Subject: [PATCH 2/2] Handle event coordinate in the user projection --- src/ol/View.js | 23 ++++++++ src/ol/interaction/DragRotateAndZoom.js | 2 +- src/ol/interaction/Interaction.js | 4 +- src/ol/interaction/PinchZoom.js | 2 +- test/spec/ol/interaction/interaction.test.js | 27 ++++++++- test/spec/ol/view.test.js | 58 ++++++++++++++++++++ 6 files changed, 110 insertions(+), 6 deletions(-) diff --git a/src/ol/View.js b/src/ol/View.js index e806119a9b..a3fde0570f 100644 --- a/src/ol/View.js +++ b/src/ol/View.js @@ -1237,6 +1237,17 @@ class View extends BaseObject { * @api */ adjustResolution(ratio, opt_anchor) { + const anchor = opt_anchor && fromUserCoordinate(opt_anchor, this.getProjection()); + this.adjustResolutionInternal(ratio, anchor); + } + + /** + * Multiply the view resolution by a ratio, optionally using an anchor. Any resolution + * constraint will apply. + * @param {number} ratio The ratio to apply on the view resolution. + * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. + */ + adjustResolutionInternal(ratio, opt_anchor) { const isMoving = this.getAnimating() || this.getInteracting(); const size = this.getSizeFromViewport_(this.getRotation()); const newResolution = this.constraints_.resolution(this.targetResolution_ * ratio, 0, size, isMoving); @@ -1448,6 +1459,18 @@ class View extends BaseObject { * @api */ endInteraction(opt_duration, opt_resolutionDirection, opt_anchor) { + const anchor = opt_anchor && fromUserCoordinate(opt_anchor, this.getProjection()); + this.endInteractionInternal(opt_duration, opt_resolutionDirection, anchor); + } + + /** + * Notify the View that an interaction has ended. The view state will be resolved + * to a stable one if needed (depending on its constraints). + * @param {number=} opt_duration Animation duration in ms. + * @param {number=} opt_resolutionDirection Which direction to zoom. + * @param {import("./coordinate.js").Coordinate=} opt_anchor The origin of the transformation. + */ + endInteractionInternal(opt_duration, opt_resolutionDirection, opt_anchor) { this.setHint(ViewHint.INTERACTING, -1); this.resolveConstraints(opt_duration, opt_resolutionDirection, opt_anchor); diff --git a/src/ol/interaction/DragRotateAndZoom.js b/src/ol/interaction/DragRotateAndZoom.js index 1329c2ac91..593bc9e4ee 100644 --- a/src/ol/interaction/DragRotateAndZoom.js +++ b/src/ol/interaction/DragRotateAndZoom.js @@ -91,7 +91,7 @@ class DragRotateAndZoom extends PointerInteraction { } this.lastAngle_ = theta; if (this.lastMagnitude_ !== undefined) { - view.adjustResolution(this.lastMagnitude_ / magnitude); + view.adjustResolutionInternal(this.lastMagnitude_ / magnitude); } if (this.lastMagnitude_ !== undefined) { this.lastScaleDelta_ = this.lastMagnitude_ / magnitude; diff --git a/src/ol/interaction/Interaction.js b/src/ol/interaction/Interaction.js index 18598ea7ff..16ff153d4d 100644 --- a/src/ol/interaction/Interaction.js +++ b/src/ol/interaction/Interaction.js @@ -122,7 +122,7 @@ export function pan(view, delta, opt_duration) { /** * @param {import("../View.js").default} view View. * @param {number} delta Delta from previous zoom level. - * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate. + * @param {import("../coordinate.js").Coordinate=} opt_anchor Anchor coordinate in the user projection. * @param {number=} opt_duration Duration. */ export function zoomByDelta(view, delta, opt_anchor, opt_duration) { @@ -138,7 +138,7 @@ export function zoomByDelta(view, delta, opt_anchor, opt_duration) { if (view.getAnimating()) { view.cancelAnimations(); } - view.animateInternal({ + view.animate({ resolution: newResolution, anchor: opt_anchor, duration: opt_duration !== undefined ? opt_duration : 250, diff --git a/src/ol/interaction/PinchZoom.js b/src/ol/interaction/PinchZoom.js index eca8c8ab9f..31aa44dd16 100644 --- a/src/ol/interaction/PinchZoom.js +++ b/src/ol/interaction/PinchZoom.js @@ -95,7 +95,7 @@ class PinchZoom extends PointerInteraction { // scale, bypass the resolution constraint map.render(); - view.adjustResolution(scaleDelta, this.anchor_); + view.adjustResolutionInternal(scaleDelta, this.anchor_); } /** diff --git a/test/spec/ol/interaction/interaction.test.js b/test/spec/ol/interaction/interaction.test.js index b83f949edb..cec796e574 100644 --- a/test/spec/ol/interaction/interaction.test.js +++ b/test/spec/ol/interaction/interaction.test.js @@ -1,7 +1,8 @@ -import Map from '../../../../src/ol/Map.js'; +import {Map, View} from '../../../../src/ol/index.js'; import EventTarget from '../../../../src/ol/events/Target.js'; -import Interaction from '../../../../src/ol/interaction/Interaction.js'; +import Interaction, {zoomByDelta} from '../../../../src/ol/interaction/Interaction.js'; import {FALSE} from '../../../../src/ol/functions.js'; +import {useGeographic, clearUserProjection} from '../../../../src/ol/proj.js'; describe('ol.interaction.Interaction', function() { @@ -87,3 +88,25 @@ describe('ol.interaction.Interaction', function() { }); }); + +describe('zoomByDelta - useGeographic', () => { + beforeEach(useGeographic); + afterEach(clearUserProjection); + + it('works with a user projection set', done => { + const view = new View({ + center: [0, 0], + zoom: 0 + }); + + const anchor = [90, 45]; + const duration = 10; + zoomByDelta(view, 1, anchor, duration); + setTimeout(() => { + const center = view.getCenter(); + expect(center[0]).to.be(45); + expect(center[1]).to.roughlyEqual(24.4698, 1e-4); + done(); + }, 2 * duration); + }); +}); diff --git a/test/spec/ol/view.test.js b/test/spec/ol/view.test.js index 96b7d84830..8f18c70030 100644 --- a/test/spec/ol/view.test.js +++ b/test/spec/ol/view.test.js @@ -8,6 +8,7 @@ import {createEmpty} from '../../../src/ol/extent.js'; import Circle from '../../../src/ol/geom/Circle.js'; import LineString from '../../../src/ol/geom/LineString.js'; import Point from '../../../src/ol/geom/Point.js'; +import {useGeographic, clearUserProjection} from '../../../src/ol/proj.js'; describe('ol.View', function() { @@ -1890,6 +1891,63 @@ describe('ol.View', function() { expect(view.getCenter()).to.eql([0, 100 - halfSize]); }); }); + + describe('#adjustZoom() - useGeographic', () => { + + beforeEach(useGeographic); + afterEach(clearUserProjection); + + it('changes view resolution', () => { + const view = new View({ + resolution: 1, + resolutions: [4, 2, 1, 0.5, 0.25] + }); + + view.adjustZoom(1); + expect(view.getResolution()).to.be(0.5); + + view.adjustZoom(-1); + expect(view.getResolution()).to.be(1); + + view.adjustZoom(2); + expect(view.getResolution()).to.be(0.25); + + view.adjustZoom(-2); + expect(view.getResolution()).to.be(1); + }); + + it('changes view resolution and center relative to the anchor', function() { + const view = new View({ + center: [0, 0], + zoom: 0 + }); + + let center; + + view.adjustZoom(1, [90, 45]); + center = view.getCenter(); + expect(center[0]).to.be(45); + expect(center[1]).to.roughlyEqual(24.4698, 1e-4); + + view.adjustZoom(-1, [90, 45]); + center = view.getCenter(); + expect(center[0]).to.roughlyEqual(0, 1e-10); + expect(center[1]).to.roughlyEqual(0, 1e-10); + + view.adjustZoom(2, [-90, -45]); + center = view.getCenter(); + expect(center[0]).to.be(-67.5); + expect(center[1]).to.roughlyEqual(-35.3836, 1e-4); + + view.adjustZoom(-2, [-90, -45]); + center = view.getCenter(); + expect(center[0]).to.roughlyEqual(0, 1e-10); + expect(center[1]).to.roughlyEqual(0, 1e-10); + + }); + + }); + }); describe('does not start unexpected animations during interaction', function() {