diff --git a/examples/geographic.html b/examples/geographic.html
new file mode 100644
index 0000000000..3993e862b5
--- /dev/null
+++ b/examples/geographic.html
@@ -0,0 +1,13 @@
+---
+layout: example.html
+title: Geographic Coordinates
+shortdesc: Using geographic coordinates for the map view.
+docs: >
+ Calling the useGeographic function in the 'ol/proj' module
+ makes it so the map view uses geographic coordinates (even if the view projection is
+ not geographic).
+tags: "geographic"
+---
+
+
+
diff --git a/examples/geographic.js b/examples/geographic.js
new file mode 100644
index 0000000000..1bebfee353
--- /dev/null
+++ b/examples/geographic.js
@@ -0,0 +1,27 @@
+import {useGeographic} from '../src/ol/proj.js';
+import Map from '../src/ol/Map.js';
+import View from '../src/ol/View.js';
+import TileLayer from '../src/ol/layer/Tile.js';
+import OSM from '../src/ol/source/OSM.js';
+
+useGeographic();
+
+const map = new Map({
+ layers: [
+ new TileLayer({
+ source: new OSM()
+ })
+ ],
+ target: 'map',
+ view: new View({
+ center: [-110, 45],
+ zoom: 8
+ })
+});
+
+const info = document.getElementById('info');
+map.on('moveend', function() {
+ const view = map.getView();
+ const center = view.getCenter();
+ info.innerText = `lon: ${center[0].toFixed(2)}, lat: ${center[1].toFixed(2)}`;
+});
diff --git a/src/ol/Overlay.js b/src/ol/Overlay.js
index 436ac13514..3fb91c38ac 100644
--- a/src/ol/Overlay.js
+++ b/src/ol/Overlay.js
@@ -418,14 +418,14 @@ class Overlay extends BaseObject {
}
if (delta[0] !== 0 || delta[1] !== 0) {
- const center = /** @type {import("./coordinate.js").Coordinate} */ (map.getView().getCenter());
+ const center = /** @type {import("./coordinate.js").Coordinate} */ (map.getView().getCenterInternal());
const centerPx = map.getPixelFromCoordinate(center);
const newCenterPx = [
centerPx[0] + delta[0],
centerPx[1] + delta[1]
];
- map.getView().animate({
+ map.getView().animateInternal({
center: map.getCoordinateFromPixel(newCenterPx),
duration: this.autoPanAnimation.duration,
easing: this.autoPanAnimation.easing
diff --git a/src/ol/View.js b/src/ol/View.js
index 253371cc78..b3abc05f89 100644
--- a/src/ol/View.js
+++ b/src/ol/View.js
@@ -19,7 +19,7 @@ import GeometryType from './geom/GeometryType.js';
import {fromExtent as polygonFromExtent} from './geom/Polygon.js';
import {clamp, modulo} from './math.js';
import {assign} from './obj.js';
-import {createProjection, METERS_PER_UNIT} from './proj.js';
+import {createProjection, METERS_PER_UNIT, toUserCoordinate, toUserExtent, fromUserCoordinate, fromUserExtent, getUserProjection} from './proj.js';
import Units from './proj/Units.js';
import {equals} from './coordinate.js';
import {easeOut} from './easing.js';
@@ -82,9 +82,9 @@ import {createMinMaxResolution} from './resolutionconstraint.js';
/**
* @typedef {Object} ViewOptions
* @property {import("./coordinate.js").Coordinate} [center] The initial center for
- * the view. The coordinate system for the center is specified with the
- * `projection` option. Layer sources will not be fetched if this is not set,
- * but the center can be set later with {@link #setCenter}.
+ * the view. If a user projection is not set, the coordinate system for the center is
+ * specified with the `projection` option. Layer sources will not be fetched if this
+ * is not set, but the center can be set later with {@link #setCenter}.
* @property {boolean|number} [constrainRotation=true] Rotation constraint.
* `false` means no constraint. `true` means no constraint, but snap to zero
* near zero. A number constrains the rotation to that number of values. For
@@ -309,6 +309,13 @@ class View extends BaseObject {
*/
this.targetRotation_;
+ if (options.center) {
+ options.center = fromUserCoordinate(options.center, this.projection_);
+ }
+ if (options.extent) {
+ options.extent = fromUserExtent(options.extent, this.projection_);
+ }
+
this.applyOptions_(options);
}
@@ -370,7 +377,7 @@ class View extends BaseObject {
};
this.setRotation(options.rotation !== undefined ? options.rotation : 0);
- this.setCenter(options.center !== undefined ? options.center : null);
+ this.setCenterInternal(options.center !== undefined ? options.center : null);
if (options.resolution !== undefined) {
this.setResolution(options.resolution);
} else if (options.zoom !== undefined) {
@@ -408,7 +415,7 @@ class View extends BaseObject {
}
// preserve center
- options.center = this.getCenter();
+ options.center = this.getCenterInternal();
// preserve rotation
options.rotation = this.getRotation();
@@ -453,14 +460,26 @@ class View extends BaseObject {
if (this.isDef() && !this.getAnimating()) {
this.resolveConstraints(0);
}
- this.animate_.apply(this, arguments);
+ const args = new Array(arguments.length);
+ for (let i = 0; i < args.length; ++i) {
+ let options = arguments[i];
+ if (options.center) {
+ options = assign({}, options);
+ options.center = fromUserCoordinate(options.center, this.getProjection());
+ }
+ if (options.anchor) {
+ options = assign({}, options);
+ options.anchor = fromUserCoordinate(options.anchor, this.getProjection());
+ }
+ args[i] = options;
+ }
+ this.animateInternal.apply(this, args);
}
/**
- * @private
* @param {...(AnimationOptions|function(boolean): void)} var_args Animation options.
*/
- animate_(var_args) {
+ animateInternal(var_args) {
let animationCount = arguments.length;
let callback;
if (animationCount > 1 && typeof arguments[animationCount - 1] === 'function') {
@@ -471,7 +490,7 @@ class View extends BaseObject {
// if view properties are not yet set, shortcut to the final state
const state = arguments[animationCount - 1];
if (state.center) {
- this.setCenter(state.center);
+ this.setCenterInternal(state.center);
}
if (state.zoom !== undefined) {
this.setZoom(state.zoom);
@@ -661,7 +680,7 @@ class View extends BaseObject {
*/
calculateCenterRotate(rotation, anchor) {
let center;
- const currentCenter = this.getCenter();
+ const currentCenter = this.getCenterInternal();
if (currentCenter !== undefined) {
center = [currentCenter[0] - anchor[0], currentCenter[1] - anchor[1]];
rotateCoordinate(center, rotation - this.getRotation());
@@ -677,7 +696,7 @@ class View extends BaseObject {
*/
calculateCenterZoom(resolution, anchor) {
let center;
- const currentCenter = this.getCenter();
+ const currentCenter = this.getCenterInternal();
const currentResolution = this.getResolution();
if (currentCenter !== undefined && currentResolution !== undefined) {
const x = anchor[0] - resolution * (anchor[0] - currentCenter[0]) / currentResolution;
@@ -717,9 +736,19 @@ class View extends BaseObject {
* @api
*/
getCenter() {
- return (
- /** @type {import("./coordinate.js").Coordinate|undefined} */ (this.get(ViewProperty.CENTER))
- );
+ const center = this.getCenterInternal();
+ if (!center) {
+ return center;
+ }
+ return toUserCoordinate(center, this.getProjection());
+ }
+
+ /**
+ * Get the view center without transforming to user projection.
+ * @return {import("./coordinate.js").Coordinate|undefined} The center of the view.
+ */
+ getCenterInternal() {
+ return /** @type {import("./coordinate.js").Coordinate|undefined} */ (this.get(ViewProperty.CENTER));
}
/**
@@ -754,8 +783,18 @@ class View extends BaseObject {
* @api
*/
calculateExtent(opt_size) {
+ const extent = this.calculateExtentInternal(opt_size);
+ return toUserExtent(extent, this.getProjection());
+ }
+
+ /**
+ * @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, the size of the
+ * first map that uses this view will be used.
+ * @return {import("./extent.js").Extent} Extent.
+ */
+ calculateExtentInternal(opt_size) {
const size = opt_size || this.getSizeFromViewport_();
- const center = /** @type {!import("./coordinate.js").Coordinate} */ (this.getCenter());
+ const center = /** @type {!import("./coordinate.js").Coordinate} */ (this.getCenterInternal());
assert(center, 1); // The view center is not defined
const resolution = /** @type {!number} */ (this.getResolution());
assert(resolution !== undefined, 2); // The view resolution is not defined
@@ -866,6 +905,17 @@ class View extends BaseObject {
* @api
*/
getResolutionForExtent(extent, opt_size) {
+ return this.getResolutionForExtentInternal(fromUserExtent(extent, this.getProjection()), opt_size);
+ }
+
+ /**
+ * Get the resolution for a provided extent (in map units) and size (in pixels).
+ * @param {import("./extent.js").Extent} extent Extent.
+ * @param {import("./size.js").Size=} opt_size Box pixel size.
+ * @return {number} The resolution at which the provided extent will render at
+ * the given size.
+ */
+ getResolutionForExtentInternal(extent, opt_size) {
const size = opt_size || this.getSizeFromViewport_();
const xResolution = getWidth(extent) / size[0];
const yResolution = getHeight(extent) / size[1];
@@ -930,7 +980,7 @@ class View extends BaseObject {
* @return {State} View state.
*/
getState() {
- const center = /** @type {import("./coordinate.js").Coordinate} */ (this.getCenter());
+ const center = /** @type {import("./coordinate.js").Coordinate} */ (this.getCenterInternal());
const projection = this.getProjection();
const resolution = /** @type {number} */ (this.getResolution());
const rotation = this.getRotation();
@@ -1014,11 +1064,8 @@ class View extends BaseObject {
* @api
*/
fit(geometryOrExtent, opt_options) {
- const options = opt_options || {};
- let size = options.size;
- if (!size) {
- size = this.getSizeFromViewport_();
- }
+ const options = assign({size: this.getSizeFromViewport_()}, opt_options || {});
+
/** @type {import("./geom/SimpleGeometry.js").default} */
let geometry;
assert(Array.isArray(geometryOrExtent) || typeof /** @type {?} */ (geometryOrExtent).getSimplifiedGeometry === 'function',
@@ -1026,15 +1073,34 @@ class View extends BaseObject {
if (Array.isArray(geometryOrExtent)) {
assert(!isEmpty(geometryOrExtent),
25); // Cannot fit empty extent provided as `geometry`
- geometry = polygonFromExtent(geometryOrExtent);
+ const extent = fromUserExtent(geometryOrExtent, this.getProjection());
+ geometry = polygonFromExtent(extent);
} else if (geometryOrExtent.getType() === GeometryType.CIRCLE) {
- geometryOrExtent = geometryOrExtent.getExtent();
- geometry = polygonFromExtent(geometryOrExtent);
- geometry.rotate(this.getRotation(), getCenter(geometryOrExtent));
+ const extent = fromUserExtent(geometryOrExtent.getExtent(), this.getProjection());
+ geometry = polygonFromExtent(extent);
+ geometry.rotate(this.getRotation(), getCenter(extent));
} else {
- geometry = geometryOrExtent;
+ const userProjection = getUserProjection();
+ if (userProjection) {
+ geometry = /** @type {import("./geom/SimpleGeometry.js").default} */ (geometry.clone().transform(userProjection, this.getProjection()));
+ } else {
+ geometry = geometryOrExtent;
+ }
}
+ this.fitInternal(geometry, options);
+ }
+
+ /**
+ * @param {import("./geom/SimpleGeometry.js").default} geometry The geometry.
+ * @param {FitOptions=} opt_options Options.
+ */
+ fitInternal(geometry, opt_options) {
+ const options = opt_options || {};
+ let size = options.size;
+ if (!size) {
+ size = this.getSizeFromViewport_();
+ }
const padding = options.padding !== undefined ? options.padding : [0, 0, 0, 0];
const nearest = options.nearest !== undefined ? options.nearest : false;
let minResolution;
@@ -1066,7 +1132,7 @@ class View extends BaseObject {
}
// calculate resolution
- let resolution = this.getResolutionForExtent(
+ let resolution = this.getResolutionForExtentInternal(
[minRotX, minRotY, maxRotX, maxRotY],
[size[0] - padding[1] - padding[3], size[1] - padding[0] - padding[2]]);
resolution = isNaN(resolution) ? minResolution :
@@ -1085,7 +1151,7 @@ class View extends BaseObject {
const callback = options.callback ? options.callback : VOID;
if (options.duration !== undefined) {
- this.animate_({
+ this.animateInternal({
resolution: resolution,
center: this.getConstrainedCenter(center, resolution),
duration: options.duration,
@@ -1107,6 +1173,15 @@ class View extends BaseObject {
* @api
*/
centerOn(coordinate, size, position) {
+ this.centerOnInternal(fromUserCoordinate(coordinate, this.getProjection()), size, position);
+ }
+
+ /**
+ * @param {import("./coordinate.js").Coordinate} coordinate Coordinate.
+ * @param {import("./size.js").Size} size Box pixel size.
+ * @param {import("./pixel.js").Pixel} position Position on the view to center on.
+ */
+ centerOnInternal(coordinate, size, position) {
// calculate rotated position
const rotation = this.getRotation();
const cosAngle = Math.cos(-rotation);
@@ -1122,14 +1197,14 @@ class View extends BaseObject {
const centerX = rotX * cosAngle - rotY * sinAngle;
const centerY = rotY * cosAngle + rotX * sinAngle;
- this.setCenter([centerX, centerY]);
+ this.setCenterInternal([centerX, centerY]);
}
/**
* @return {boolean} Is defined.
*/
isDef() {
- return !!this.getCenter() && this.getResolution() !== undefined;
+ return !!this.getCenterInternal() && this.getResolution() !== undefined;
}
/**
@@ -1138,10 +1213,19 @@ class View extends BaseObject {
* @api
*/
adjustCenter(deltaCoordinates) {
- const center = this.targetCenter_;
+ const center = toUserCoordinate(this.targetCenter_, this.getProjection());
this.setCenter([center[0] + deltaCoordinates[0], center[1] + deltaCoordinates[1]]);
}
+ /**
+ * Adds relative coordinates to the center of the view. Any extent constraint will apply.
+ * @param {import("./coordinate.js").Coordinate} deltaCoordinates Relative value to add.
+ */
+ adjustCenterInternal(deltaCoordinates) {
+ const center = this.targetCenter_;
+ this.setCenterInternal([center[0] + deltaCoordinates[0], center[1] + deltaCoordinates[1]]);
+ }
+
/**
* Multiply the view resolution by a ratio, optionally using an anchor. Any resolution
* constraint will apply.
@@ -1181,6 +1265,17 @@ class View extends BaseObject {
* @api
*/
adjustRotation(delta, opt_anchor) {
+ if (opt_anchor) {
+ opt_anchor = fromUserCoordinate(opt_anchor, this.getProjection());
+ }
+ this.adjustRotationInternal(delta, opt_anchor);
+ }
+
+ /**
+ * @param {number} delta Relative value to add to the zoom rotation, in radians.
+ * @param {import("./coordinate.js").Coordinate=} opt_anchor The rotation center.
+ */
+ adjustRotationInternal(delta, opt_anchor) {
const isMoving = this.getAnimating() || this.getInteracting();
const newRotation = this.constraints_.rotation(this.targetRotation_ + delta, isMoving);
if (opt_anchor !== undefined) {
@@ -1197,6 +1292,14 @@ class View extends BaseObject {
* @api
*/
setCenter(center) {
+ this.setCenterInternal(fromUserCoordinate(center, this.getProjection()));
+ }
+
+ /**
+ * Set the center using the view projection (not the user projection).
+ * @param {import("./coordinate.js").Coordinate|undefined} center The center of the view.
+ */
+ setCenterInternal(center) {
this.targetCenter_ = center;
this.applyTargetState_();
}
@@ -1303,14 +1406,14 @@ class View extends BaseObject {
if (this.getResolution() !== newResolution ||
this.getRotation() !== newRotation ||
- !this.getCenter() ||
- !equals(this.getCenter(), newCenter)) {
+ !this.getCenterInternal() ||
+ !equals(this.getCenterInternal(), newCenter)) {
if (this.getAnimating()) {
this.cancelAnimations();
}
- this.animate_({
+ this.animateInternal({
rotation: newRotation,
center: newCenter,
resolution: newResolution,
diff --git a/src/ol/control/OverviewMap.js b/src/ol/control/OverviewMap.js
index ed889c5717..6a3de53aae 100644
--- a/src/ol/control/OverviewMap.js
+++ b/src/ol/control/OverviewMap.js
@@ -11,6 +11,7 @@ import Overlay from '../Overlay.js';
import OverlayPositioning from '../OverlayPositioning.js';
import ViewProperty from '../ViewProperty.js';
import Control from './Control.js';
+import {fromExtent as polygonFromExtent} from '../geom/Polygon.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE, CLASS_COLLAPSED} from '../css.js';
import {replaceNode} from '../dom.js';
import {listen, listenOnce} from '../events.js';
@@ -230,7 +231,7 @@ class OverviewMap extends Control {
const endMoving = function(event) {
const coordinates = ovmap.getEventCoordinate(event);
- scope.getMap().getView().setCenter(coordinates);
+ scope.getMap().getView().setCenterInternal(coordinates);
window.removeEventListener('mousemove', move);
window.removeEventListener('mouseup', endMoving);
@@ -346,7 +347,7 @@ class OverviewMap extends Control {
const mapSize = /** @type {import("../size.js").Size} */ (map.getSize());
const view = map.getView();
- const extent = view.calculateExtent(mapSize);
+ const extent = view.calculateExtentInternal(mapSize);
if (this.viewExtent_ && equalsExtent(extent, this.viewExtent_)) {
// repeats of the same extent may indicate constraint conflicts leading to an endless cycle
@@ -357,7 +358,7 @@ class OverviewMap extends Control {
const ovmapSize = /** @type {import("../size.js").Size} */ (ovmap.getSize());
const ovview = ovmap.getView();
- const ovextent = ovview.calculateExtent(ovmapSize);
+ const ovextent = ovview.calculateExtentInternal(ovmapSize);
const topLeftPixel =
ovmap.getPixelFromCoordinate(getTopLeft(extent));
@@ -396,7 +397,7 @@ class OverviewMap extends Control {
const mapSize = /** @type {import("../size.js").Size} */ (map.getSize());
const view = map.getView();
- const extent = view.calculateExtent(mapSize);
+ const extent = view.calculateExtentInternal(mapSize);
const ovview = ovmap.getView();
@@ -407,7 +408,7 @@ class OverviewMap extends Control {
MAX_RATIO / MIN_RATIO) / Math.LN2;
const ratio = 1 / (Math.pow(2, steps / 2) * MIN_RATIO);
scaleFromCenter(extent, ratio);
- ovview.fit(extent);
+ ovview.fitInternal(polygonFromExtent(extent));
}
/**
@@ -423,7 +424,7 @@ class OverviewMap extends Control {
const ovview = ovmap.getView();
- ovview.setCenter(view.getCenter());
+ ovview.setCenterInternal(view.getCenterInternal());
}
/**
@@ -448,7 +449,7 @@ class OverviewMap extends Control {
const overlay = this.boxOverlay_;
const box = this.boxOverlay_.getElement();
- const center = view.getCenter();
+ const center = view.getCenterInternal();
const resolution = view.getResolution();
const ovresolution = ovview.getResolution();
const width = mapSize[0] * resolution / ovresolution;
diff --git a/src/ol/control/ZoomSlider.js b/src/ol/control/ZoomSlider.js
index b669df0faa..07397ffc77 100644
--- a/src/ol/control/ZoomSlider.js
+++ b/src/ol/control/ZoomSlider.js
@@ -204,7 +204,7 @@ class ZoomSlider extends Control {
const resolution = this.getResolutionForPosition_(relativePosition);
const zoom = view.getConstrainedZoom(view.getZoomForResolution(resolution));
- view.animate({
+ view.animateInternal({
zoom: zoom,
duration: this.duration_,
easing: easeOut
diff --git a/src/ol/control/ZoomToExtent.js b/src/ol/control/ZoomToExtent.js
index 7d784d044f..32f59de1a9 100644
--- a/src/ol/control/ZoomToExtent.js
+++ b/src/ol/control/ZoomToExtent.js
@@ -2,6 +2,7 @@
* @module ol/control/ZoomToExtent
*/
import EventType from '../events/EventType.js';
+import {fromExtent as polygonFromExtent} from '../geom/Polygon.js';
import Control from './Control.js';
import {CLASS_CONTROL, CLASS_UNSELECTABLE} from '../css.js';
@@ -80,7 +81,7 @@ class ZoomToExtent extends Control {
const map = this.getMap();
const view = map.getView();
const extent = !this.extent ? view.getProjection().getExtent() : this.extent;
- view.fit(extent);
+ view.fitInternal(polygonFromExtent(extent));
}
}
diff --git a/src/ol/interaction/DragPan.js b/src/ol/interaction/DragPan.js
index e52ed731d5..941d628d5a 100644
--- a/src/ol/interaction/DragPan.js
+++ b/src/ol/interaction/DragPan.js
@@ -92,7 +92,7 @@ class DragPan extends PointerInteraction {
const view = map.getView();
scaleCoordinate(delta, view.getResolution());
rotateCoordinate(delta, view.getRotation());
- view.adjustCenter(delta);
+ view.adjustCenterInternal(delta);
}
} else if (this.kinetic_) {
// reset so we don't overestimate the kinetic energy after
@@ -113,13 +113,13 @@ class DragPan extends PointerInteraction {
if (!this.noKinetic_ && this.kinetic_ && this.kinetic_.end()) {
const distance = this.kinetic_.getDistance();
const angle = this.kinetic_.getAngle();
- const center = /** @type {!import("../coordinate.js").Coordinate} */ (view.getCenter());
+ const center = view.getCenterInternal();
const centerpx = map.getPixelFromCoordinate(center);
const dest = map.getCoordinateFromPixel([
centerpx[0] - distance * Math.cos(angle),
centerpx[1] - distance * Math.sin(angle)
]);
- view.animate({
+ view.animateInternal({
center: view.getConstrainedCenter(dest),
duration: 500,
easing: easeOut
diff --git a/src/ol/interaction/DragRotate.js b/src/ol/interaction/DragRotate.js
index d7fc11cfd6..2749785fd6 100644
--- a/src/ol/interaction/DragRotate.js
+++ b/src/ol/interaction/DragRotate.js
@@ -78,7 +78,7 @@ class DragRotate extends PointerInteraction {
Math.atan2(size[1] / 2 - offset[1], offset[0] - size[0] / 2);
if (this.lastAngle_ !== undefined) {
const delta = theta - this.lastAngle_;
- view.adjustRotation(-delta);
+ view.adjustRotationInternal(-delta);
}
this.lastAngle_ = theta;
}
diff --git a/src/ol/interaction/DragRotateAndZoom.js b/src/ol/interaction/DragRotateAndZoom.js
index 3e750df327..1329c2ac91 100644
--- a/src/ol/interaction/DragRotateAndZoom.js
+++ b/src/ol/interaction/DragRotateAndZoom.js
@@ -87,7 +87,7 @@ class DragRotateAndZoom extends PointerInteraction {
const view = map.getView();
if (this.lastAngle_ !== undefined) {
const angleDelta = this.lastAngle_ - theta;
- view.adjustRotation(angleDelta);
+ view.adjustRotationInternal(angleDelta);
}
this.lastAngle_ = theta;
if (this.lastMagnitude_ !== undefined) {
diff --git a/src/ol/interaction/DragZoom.js b/src/ol/interaction/DragZoom.js
index 92737648df..37cabeb78b 100644
--- a/src/ol/interaction/DragZoom.js
+++ b/src/ol/interaction/DragZoom.js
@@ -73,20 +73,20 @@ function onBoxEnd() {
let extent = this.getGeometry().getExtent();
if (this.out_) {
- const mapExtent = view.calculateExtent(size);
+ const mapExtent = view.calculateExtentInternal(size);
const boxPixelExtent = createOrUpdateFromCoordinates([
map.getPixelFromCoordinate(getBottomLeft(extent)),
map.getPixelFromCoordinate(getTopRight(extent))]);
- const factor = view.getResolutionForExtent(boxPixelExtent, size);
+ const factor = view.getResolutionForExtentInternal(boxPixelExtent, size);
scaleFromCenter(mapExtent, 1 / factor);
extent = mapExtent;
}
- const resolution = view.getConstrainedResolution(view.getResolutionForExtent(extent, size));
+ const resolution = view.getConstrainedResolution(view.getResolutionForExtentInternal(extent, size));
const center = view.getConstrainedCenter(getCenter(extent), resolution);
- view.animate({
+ view.animateInternal({
resolution: resolution,
center: center,
duration: this.duration_,
diff --git a/src/ol/interaction/Interaction.js b/src/ol/interaction/Interaction.js
index 7a3cc810a2..18598ea7ff 100644
--- a/src/ol/interaction/Interaction.js
+++ b/src/ol/interaction/Interaction.js
@@ -108,10 +108,10 @@ class Interaction extends BaseObject {
* @param {number=} opt_duration Duration.
*/
export function pan(view, delta, opt_duration) {
- const currentCenter = view.getCenter();
+ const currentCenter = view.getCenterInternal();
if (currentCenter) {
const center = [currentCenter[0] + delta[0], currentCenter[1] + delta[1]];
- view.animate_({
+ view.animateInternal({
duration: opt_duration !== undefined ? opt_duration : 250,
easing: linear,
center: view.getConstrainedCenter(center)
@@ -138,7 +138,7 @@ export function zoomByDelta(view, delta, opt_anchor, opt_duration) {
if (view.getAnimating()) {
view.cancelAnimations();
}
- view.animate({
+ view.animateInternal({
resolution: newResolution,
anchor: opt_anchor,
duration: opt_duration !== undefined ? opt_duration : 250,
diff --git a/src/ol/interaction/PinchRotate.js b/src/ol/interaction/PinchRotate.js
index c1143ba312..3977d82357 100644
--- a/src/ol/interaction/PinchRotate.js
+++ b/src/ol/interaction/PinchRotate.js
@@ -117,7 +117,7 @@ class PinchRotate extends PointerInteraction {
// rotate
if (this.rotating_) {
map.render();
- view.adjustRotation(rotationDelta, this.anchor_);
+ view.adjustRotationInternal(rotationDelta, this.anchor_);
}
}
diff --git a/src/ol/proj.js b/src/ol/proj.js
index 763d0210b0..cd0fefea0e 100644
--- a/src/ol/proj.js
+++ b/src/ol/proj.js
@@ -498,6 +498,105 @@ export function transformWithProjections(point, sourceProjection, destinationPro
return transformFunc(point);
}
+/**
+ * @type {Projection}
+ */
+let userProjection = null;
+
+/**
+ * Set the projection for coordinates supplied from and returned by API methods.
+ * Note that this method is not yet a part of the stable API. Support for user
+ * projections is not yet complete and should be considered experimental.
+ * @param {ProjectionLike} projection The user projection.
+ */
+export function setUserProjection(projection) {
+ userProjection = get(projection);
+}
+
+/**
+ * Clear the user projection if set. Note that this method is not yet a part of
+ * the stable API. Support for user projections is not yet complete and should
+ * be considered experimental.
+ */
+export function clearUserProjection() {
+ userProjection = null;
+}
+
+/**
+ * Get the projection for coordinates supplied from and returned by API methods.
+ * Note that this method is not yet a part of the stable API. Support for user
+ * projections is not yet complete and should be considered experimental.
+ * @returns {Projection} The user projection (or null if not set).
+ */
+export function getUserProjection() {
+ return userProjection;
+}
+
+/**
+ * Use geographic coordinates (WGS-84 datum) in API methods. Note that this
+ * method is not yet a part of the stable API. Support for user projections is
+ * not yet complete and should be considered experimental.
+ */
+export function useGeographic() {
+ setUserProjection('EPSG:4326');
+}
+
+/**
+ * Return a coordinate transformed into the user projection. If no user projection
+ * is set, the original coordinate is returned.
+ * @param {Array} coordinate Input coordinate.
+ * @param {ProjectionLike} sourceProjection The input coordinate projection.
+ * @returns {Array} The input coordinate in the user projection.
+ */
+export function toUserCoordinate(coordinate, sourceProjection) {
+ if (!userProjection) {
+ return coordinate;
+ }
+ return transform(coordinate, sourceProjection, userProjection);
+}
+
+/**
+ * Return a coordinate transformed from the user projection. If no user projection
+ * is set, the original coordinate is returned.
+ * @param {Array} coordinate Input coordinate.
+ * @param {ProjectionLike} destProjection The destination projection.
+ * @returns {Array} The input coordinate transformed.
+ */
+export function fromUserCoordinate(coordinate, destProjection) {
+ if (!userProjection) {
+ return coordinate;
+ }
+ return transform(coordinate, userProjection, destProjection);
+}
+
+/**
+ * Return an extent transformed into the user projection. If no user projection
+ * is set, the original extent is returned.
+ * @param {import("./extent.js").Extent} extent Input extent.
+ * @param {ProjectionLike} sourceProjection The input extent projection.
+ * @returns {import("./extent.js").Extent} The input extent in the user projection.
+ */
+export function toUserExtent(extent, sourceProjection) {
+ if (!userProjection) {
+ return extent;
+ }
+ return transformExtent(extent, sourceProjection, userProjection);
+}
+
+/**
+ * Return an extent transformed from the user projection. If no user projection
+ * is set, the original extent is returned.
+ * @param {import("./extent.js").Extent} extent Input extent.
+ * @param {ProjectionLike} destProjection The destination projection.
+ * @returns {import("./extent.js").Extent} The input extent transformed.
+ */
+export function fromUserExtent(extent, destProjection) {
+ if (!userProjection) {
+ return extent;
+ }
+ return transformExtent(extent, userProjection, destProjection);
+}
+
/**
* Add transforms to and from EPSG:4326 and EPSG:3857. This function is called
* by when this module is executed and should only need to be called again after
diff --git a/test/spec/ol/interaction/dragzoom.test.js b/test/spec/ol/interaction/dragzoom.test.js
index 9746ee25ca..e02cc89d29 100644
--- a/test/spec/ol/interaction/dragzoom.test.js
+++ b/test/spec/ol/interaction/dragzoom.test.js
@@ -78,7 +78,7 @@ describe('ol.interaction.DragZoom', function() {
interaction.onBoxEnd_();
setTimeout(function() {
const view = map.getView();
- const center = view.getCenter();
+ const center = view.getCenterInternal();
expect(center).to.eql(getCenter(extent));
done();
}, 50);
diff --git a/test/spec/ol/interaction/keyboardpan.test.js b/test/spec/ol/interaction/keyboardpan.test.js
index 8a6c59da16..b9c019e322 100644
--- a/test/spec/ol/interaction/keyboardpan.test.js
+++ b/test/spec/ol/interaction/keyboardpan.test.js
@@ -24,7 +24,7 @@ describe('ol.interaction.KeyboardPan', function() {
describe('handleEvent()', function() {
it('pans on arrow keys', function() {
const view = map.getView();
- const spy = sinon.spy(view, 'animate_');
+ const spy = sinon.spy(view, 'animateInternal');
const event = new MapBrowserEvent('keydown', map, {
type: 'keydown',
target: map.getTargetElement(),
@@ -51,7 +51,7 @@ describe('ol.interaction.KeyboardPan', function() {
expect(spy.getCall(3).args[0].center).to.eql([128, 0]);
view.setCenter([0, 0]);
- view.animate_.restore();
+ view.animateInternal.restore();
});
});
diff --git a/test/spec/ol/interaction/keyboardzoom.test.js b/test/spec/ol/interaction/keyboardzoom.test.js
index 8989ba9256..e5c6885f81 100644
--- a/test/spec/ol/interaction/keyboardzoom.test.js
+++ b/test/spec/ol/interaction/keyboardzoom.test.js
@@ -24,7 +24,7 @@ describe('ol.interaction.KeyboardZoom', function() {
describe('handleEvent()', function() {
it('zooms on + and - keys', function() {
const view = map.getView();
- const spy = sinon.spy(view, 'animate');
+ const spy = sinon.spy(view, 'animateInternal');
const event = new MapBrowserEvent('keydown', map, {
type: 'keydown',
target: map.getTargetElement(),
@@ -41,7 +41,7 @@ describe('ol.interaction.KeyboardZoom', function() {
expect(spy.getCall(1).args[0].resolution).to.eql(4);
view.setResolution(2);
- view.animate.restore();
+ view.animateInternal.restore();
});
});
diff --git a/test/spec/ol/interaction/mousewheelzoom.test.js b/test/spec/ol/interaction/mousewheelzoom.test.js
index c2d1d10cab..ee1858bf5d 100644
--- a/test/spec/ol/interaction/mousewheelzoom.test.js
+++ b/test/spec/ol/interaction/mousewheelzoom.test.js
@@ -99,20 +99,20 @@ describe('ol.interaction.MouseWheelZoom', function() {
});
}
- describe('spying on view.animate()', function() {
+ describe('spying on view.animateInternal()', function() {
let view;
beforeEach(function() {
view = map.getView();
- sinon.spy(view, 'animate');
+ sinon.spy(view, 'animateInternal');
});
afterEach(function() {
- view.animate.restore();
+ view.animateInternal.restore();
});
it('works in DOM_DELTA_LINE mode (wheel)', function(done) {
map.once('postrender', function() {
- const call = view.animate.getCall(0);
+ const call = view.animateInternal.getCall(0);
expect(call.args[0].resolution).to.be(2);
expect(call.args[0].anchor).to.eql([0, 0]);
done();
@@ -132,7 +132,7 @@ describe('ol.interaction.MouseWheelZoom', function() {
it('works on all browsers (wheel)', function(done) {
map.once('postrender', function() {
- const call = view.animate.getCall(0);
+ const call = view.animateInternal.getCall(0);
expect(call.args[0].resolution).to.be(2);
expect(call.args[0].anchor).to.eql([0, 0]);
done();
diff --git a/test/spec/ol/proj.test.js b/test/spec/ol/proj.test.js
index 797a5a66d4..986767ad2d 100644
--- a/test/spec/ol/proj.test.js
+++ b/test/spec/ol/proj.test.js
@@ -1,4 +1,12 @@
import {
+ useGeographic,
+ getUserProjection,
+ setUserProjection,
+ clearUserProjection,
+ toUserCoordinate,
+ fromUserCoordinate,
+ toUserExtent,
+ fromUserExtent,
addCommon,
clearAllProjections,
equivalent,
@@ -22,9 +30,114 @@ describe('ol.proj', function() {
afterEach(function() {
clearAllProjections();
+ clearUserProjection();
addCommon();
});
+ describe('useGeographic()', function() {
+ it('sets the user projection to Geographic/WGS-84', function() {
+ useGeographic();
+ const projection = getUserProjection();
+ expect(projection).to.be(getProjection('EPSG:4326'));
+ });
+ });
+
+ describe('getUserProjection()', function() {
+ it('returns null by default', function() {
+ expect(getUserProjection()).to.be(null);
+ });
+
+ it('returns the user projection if set', function() {
+ const projection = getProjection('EPSG:4326');
+ setUserProjection(projection);
+ expect(getUserProjection()).to.be(projection);
+ });
+ });
+
+ describe('setUserProjection()', function() {
+ it('accepts a string identifier', function() {
+ const projection = getProjection('EPSG:4326');
+ setUserProjection('EPSG:4326');
+ expect(getUserProjection()).to.be(projection);
+ });
+ });
+
+ describe('clearUserProjection()', function() {
+ it('clears the user projection', function() {
+ useGeographic();
+ clearUserProjection();
+ expect(getUserProjection()).to.be(null);
+ });
+ });
+
+ describe('toUserCoordinate()', function() {
+ it('transforms a point to the user projection', function() {
+ useGeographic();
+ const coordinate = fromLonLat([-110, 45]);
+ const user = toUserCoordinate(coordinate, 'EPSG:3857');
+ const transformed = transform(coordinate, 'EPSG:3857', 'EPSG:4326');
+ expect(user).to.eql(transformed);
+ expect(user).not.to.eql(coordinate);
+ });
+
+ it('returns the original if no user projection is set', function() {
+ const coordinate = fromLonLat([-110, 45]);
+ const user = toUserCoordinate(coordinate, 'EPSG:3857');
+ expect(user).to.be(coordinate);
+ });
+ });
+
+ describe('fromUserCoordinate()', function() {
+ it('transforms a point from the user projection', function() {
+ useGeographic();
+ const user = [-110, 45];
+ const coordinate = fromUserCoordinate(user, 'EPSG:3857');
+ const transformed = transform(user, 'EPSG:4326', 'EPSG:3857');
+ expect(coordinate).to.eql(transformed);
+ expect(user).not.to.eql(coordinate);
+ });
+
+ it('returns the original if no user projection is set', function() {
+ const user = fromLonLat([-110, 45]);
+ const coordinate = fromUserCoordinate(user, 'EPSG:3857');
+ expect(coordinate).to.be(user);
+ });
+ });
+
+ describe('toUserExtent()', function() {
+ it('transforms an extent to the user projection', function() {
+ useGeographic();
+ const extent = transformExtent([-110, 45, -100, 50], 'EPSG:4326', 'EPSG:3857');
+ const user = toUserExtent(extent, 'EPSG:3857');
+ const transformed = transformExtent(extent, 'EPSG:3857', 'EPSG:4326');
+ expect(user).to.eql(transformed);
+ expect(user).not.to.eql(extent);
+ });
+
+ it('returns the original if no user projection is set', function() {
+ const extent = transformExtent([-110, 45, -100, 50], 'EPSG:4326', 'EPSG:3857');
+ const user = toUserExtent(extent, 'EPSG:3857');
+ expect(user).to.be(extent);
+ });
+ });
+
+ describe('fromUserExtent()', function() {
+ it('transforms an extent from the user projection', function() {
+ useGeographic();
+ const user = [-110, 45, -100, 50];
+ const extent = fromUserExtent(user, 'EPSG:3857');
+ const transformed = transformExtent(user, 'EPSG:4326', 'EPSG:3857');
+ expect(extent).to.eql(transformed);
+ expect(extent).not.to.eql(user);
+ });
+
+ it('returns the original if no user projection is set', function() {
+ const user = transformExtent([-110, 45, -100, 50], 'EPSG:4326', 'EPSG:3857');
+ const extent = fromUserExtent(user, 'EPSG:3857');
+ expect(extent).to.be(user);
+ });
+ });
+
describe('toLonLat()', function() {
const cases = [{
from: [0, 0],