Add padding option for View
This commit is contained in:
@@ -11,6 +11,11 @@ docs: >
|
|||||||
view's <code>centerOn</code> method is used to position a coordinate (Lausanne)
|
view's <code>centerOn</code> method is used to position a coordinate (Lausanne)
|
||||||
at a specific pixel location (the center of the black box).
|
at a specific pixel location (the center of the black box).
|
||||||
<p>Use <code>Alt+Shift+Drag</code> to rotate the map.</p>
|
<p>Use <code>Alt+Shift+Drag</code> to rotate the map.</p>
|
||||||
|
<p><b>Note:</b> This example does not shift the view center. So the zoom
|
||||||
|
controls and rotating the map will still use the center of the viewport as anchor.
|
||||||
|
To shift the whole view based on a padding, use the `padding` option on the view,
|
||||||
|
as shown in the <a href="view-padding.html">view-padding.html</a> example.
|
||||||
|
</p>
|
||||||
tags: "center, rotation, openstreetmap"
|
tags: "center, rotation, openstreetmap"
|
||||||
---
|
---
|
||||||
<div class="mapcontainer">
|
<div class="mapcontainer">
|
||||||
|
|||||||
54
examples/view-padding.css
Normal file
54
examples/view-padding.css
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
.mapcontainer {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.map {
|
||||||
|
width: 1000px;
|
||||||
|
height: 600px;
|
||||||
|
}
|
||||||
|
.map .ol-zoom {
|
||||||
|
top: 178px;
|
||||||
|
left: 158px;
|
||||||
|
}
|
||||||
|
.map .ol-rotate {
|
||||||
|
top: 178px;
|
||||||
|
right: 58px;
|
||||||
|
}
|
||||||
|
.map .ol-attribution,
|
||||||
|
.map .ol-attribution.ol-uncollapsible {
|
||||||
|
bottom: 30px;
|
||||||
|
right: 50px;
|
||||||
|
}
|
||||||
|
.padding-top {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0px;
|
||||||
|
width: 1000px;
|
||||||
|
height: 170px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
.padding-left {
|
||||||
|
position: absolute;
|
||||||
|
top: 170px;
|
||||||
|
left: 0;
|
||||||
|
width: 150px;
|
||||||
|
height: 400px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
.padding-right {
|
||||||
|
position: absolute;
|
||||||
|
top: 170px;
|
||||||
|
left: 950px;
|
||||||
|
width: 50px;
|
||||||
|
height: 400px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
.padding-bottom {
|
||||||
|
position: absolute;
|
||||||
|
top: 570px;
|
||||||
|
left: 0px;
|
||||||
|
width: 1000px;
|
||||||
|
height: 30px;
|
||||||
|
background: rgba(255, 255, 255, 0.5);
|
||||||
|
}
|
||||||
|
|
||||||
23
examples/view-padding.html
Normal file
23
examples/view-padding.html
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
layout: example.html
|
||||||
|
title: View Padding
|
||||||
|
shortdesc: This example demonstrates the use of the view's padding option.
|
||||||
|
docs: >
|
||||||
|
This example demonstrates how a map's view can be configured to accommodate
|
||||||
|
for viewport space covered by other elements.
|
||||||
|
If the map viewport is partially covered with other content (overlays) along
|
||||||
|
its edges, the `padding` option allows to shift the center of the viewport away from
|
||||||
|
that content. The shifted viewport center will also be the anchor for zooming in and
|
||||||
|
out with the Zoom controls, and for rotating.
|
||||||
|
<p>Use <code>Alt+Shift+Drag</code> to rotate the map.</p>
|
||||||
|
tags: "center, padding, view, shift"
|
||||||
|
---
|
||||||
|
<div class="mapcontainer">
|
||||||
|
<div id="map" class="map"></div>
|
||||||
|
<div class="padding-top"></div>
|
||||||
|
<div class="padding-left"></div>
|
||||||
|
<div class="padding-right"></div>
|
||||||
|
<div class="padding-bottom"></div>
|
||||||
|
</div>
|
||||||
|
<button id="zoomtoswitzerland">Zoom to Switzerland</button>
|
||||||
|
<button id="centerlausanne">Center on Lausanne</button>
|
||||||
73
examples/view-padding.js
Normal file
73
examples/view-padding.js
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||||
|
import Map from '../src/ol/Map.js';
|
||||||
|
import View from '../src/ol/View.js';
|
||||||
|
import {Circle as CircleStyle, Fill, Stroke, Style} from '../src/ol/style.js';
|
||||||
|
import {OSM, Vector as VectorSource} from '../src/ol/source.js';
|
||||||
|
import {Tile as TileLayer, Vector as VectorLayer} from '../src/ol/layer.js';
|
||||||
|
import {fromLonLat} from '../src/ol/proj.js';
|
||||||
|
|
||||||
|
/** @type {VectorSource<import("../src/ol/geom/SimpleGeometry.js").default>} */
|
||||||
|
const source = new VectorSource({
|
||||||
|
url: 'data/geojson/switzerland.geojson',
|
||||||
|
format: new GeoJSON(),
|
||||||
|
});
|
||||||
|
const style = new Style({
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#319FD3',
|
||||||
|
width: 1,
|
||||||
|
}),
|
||||||
|
image: new CircleStyle({
|
||||||
|
radius: 5,
|
||||||
|
fill: new Fill({
|
||||||
|
color: 'rgba(255, 255, 255, 0.6)',
|
||||||
|
}),
|
||||||
|
stroke: new Stroke({
|
||||||
|
color: '#319FD3',
|
||||||
|
width: 1,
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const vectorLayer = new VectorLayer({
|
||||||
|
source: source,
|
||||||
|
style: style,
|
||||||
|
});
|
||||||
|
const view = new View({
|
||||||
|
center: fromLonLat([6.6339863, 46.5193823]),
|
||||||
|
padding: [170, 50, 30, 150],
|
||||||
|
zoom: 6,
|
||||||
|
});
|
||||||
|
const map = new Map({
|
||||||
|
layers: [
|
||||||
|
new TileLayer({
|
||||||
|
source: new OSM(),
|
||||||
|
}),
|
||||||
|
vectorLayer,
|
||||||
|
],
|
||||||
|
target: 'map',
|
||||||
|
view: view,
|
||||||
|
});
|
||||||
|
|
||||||
|
const zoomtoswitzerland = document.getElementById('zoomtoswitzerland');
|
||||||
|
zoomtoswitzerland.addEventListener(
|
||||||
|
'click',
|
||||||
|
function () {
|
||||||
|
const feature = source.getFeatures()[0];
|
||||||
|
const polygon = feature.getGeometry();
|
||||||
|
view.fit(polygon);
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const centerlausanne = document.getElementById('centerlausanne');
|
||||||
|
centerlausanne.addEventListener(
|
||||||
|
'click',
|
||||||
|
function () {
|
||||||
|
const feature = source.getFeatures()[1];
|
||||||
|
const point = feature.getGeometry();
|
||||||
|
view.setCenter(point.getCoordinates());
|
||||||
|
},
|
||||||
|
false
|
||||||
|
);
|
||||||
@@ -175,6 +175,10 @@ import {fromExtent as polygonFromExtent} from './geom/Polygon.js';
|
|||||||
* level used to calculate the initial resolution for the view.
|
* level used to calculate the initial resolution for the view.
|
||||||
* @property {number} [zoomFactor=2] The zoom factor used to compute the
|
* @property {number} [zoomFactor=2] The zoom factor used to compute the
|
||||||
* corresponding resolution.
|
* corresponding resolution.
|
||||||
|
* @property {!Array<number>} [padding=[0, 0, 0, 0]] Padding (in css pixels).
|
||||||
|
* If the map viewport is partially covered with other content (overlays) along
|
||||||
|
* its edges, this setting allows to shift the center of the viewport away from
|
||||||
|
* that content. The order of the values is top, right, bottom, left.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -394,6 +398,17 @@ class View extends BaseObject {
|
|||||||
*/
|
*/
|
||||||
this.resolutions_ = options.resolutions;
|
this.resolutions_ = options.resolutions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Padding (in css pixels).
|
||||||
|
* If the map viewport is partially covered with other content (overlays) along
|
||||||
|
* its edges, this setting allows to shift the center of the viewport away from that
|
||||||
|
* content. The order of the values in the array is top, right, bottom, left.
|
||||||
|
* The default is no padding, which is equivalent to `[0, 0, 0, 0]`.
|
||||||
|
* @type {Array<number>|undefined}
|
||||||
|
* @api
|
||||||
|
*/
|
||||||
|
this.padding = options.padding;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {number}
|
* @type {number}
|
||||||
@@ -901,8 +916,8 @@ class View extends BaseObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {import("./size.js").Size=} opt_size Box pixel size. If not provided, the size of the
|
* @param {import("./size.js").Size=} opt_size Box pixel size. If not provided,
|
||||||
* first map that uses this view will be used.
|
* the map's last known viewport size will be used.
|
||||||
* @return {import("./extent.js").Extent} Extent.
|
* @return {import("./extent.js").Extent} Extent.
|
||||||
*/
|
*/
|
||||||
calculateExtentInternal(opt_size) {
|
calculateExtentInternal(opt_size) {
|
||||||
@@ -1098,11 +1113,36 @@ class View extends BaseObject {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the size of the viewport minus padding.
|
||||||
|
* @private
|
||||||
|
* @return {import("./size.js").Size} Viewport size reduced by the padding.
|
||||||
|
*/
|
||||||
|
getViewportSizeMinusPadding_() {
|
||||||
|
let size = this.getViewportSize_();
|
||||||
|
const padding = this.padding;
|
||||||
|
if (padding) {
|
||||||
|
size = [
|
||||||
|
size[0] - padding[1] - padding[3],
|
||||||
|
size[1] - padding[0] - padding[2],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return {State} View state.
|
* @return {State} View state.
|
||||||
*/
|
*/
|
||||||
getState() {
|
getState() {
|
||||||
const center = /** @type {import("./coordinate.js").Coordinate} */ (this.getCenterInternal());
|
let center = /** @type {import("./coordinate.js").Coordinate} */ (this.getCenterInternal());
|
||||||
|
const padding = this.padding;
|
||||||
|
if (padding) {
|
||||||
|
const reducedSize = this.getViewportSizeMinusPadding_();
|
||||||
|
center = this.calculateShiftedCenter(center, this.getViewportSize_(), [
|
||||||
|
reducedSize[0] / 2 + padding[3],
|
||||||
|
reducedSize[1] / 2 + padding[0],
|
||||||
|
]);
|
||||||
|
}
|
||||||
const projection = this.getProjection();
|
const projection = this.getProjection();
|
||||||
const resolution = /** @type {number} */ (this.getResolution());
|
const resolution = /** @type {number} */ (this.getResolution());
|
||||||
const rotation = this.getRotation();
|
const rotation = this.getRotation();
|
||||||
@@ -1196,8 +1236,6 @@ class View extends BaseObject {
|
|||||||
* @api
|
* @api
|
||||||
*/
|
*/
|
||||||
fit(geometryOrExtent, opt_options) {
|
fit(geometryOrExtent, opt_options) {
|
||||||
const options = assign({size: this.getViewportSize_()}, opt_options || {});
|
|
||||||
|
|
||||||
/** @type {import("./geom/SimpleGeometry.js").default} */
|
/** @type {import("./geom/SimpleGeometry.js").default} */
|
||||||
let geometry;
|
let geometry;
|
||||||
assert(
|
assert(
|
||||||
@@ -1228,7 +1266,7 @@ class View extends BaseObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fitInternal(geometry, options);
|
this.fitInternal(geometry, opt_options);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1239,7 +1277,7 @@ class View extends BaseObject {
|
|||||||
const options = opt_options || {};
|
const options = opt_options || {};
|
||||||
let size = options.size;
|
let size = options.size;
|
||||||
if (!size) {
|
if (!size) {
|
||||||
size = this.getViewportSize_();
|
size = this.getViewportSizeMinusPadding_();
|
||||||
}
|
}
|
||||||
const padding =
|
const padding =
|
||||||
options.padding !== undefined ? options.padding : [0, 0, 0, 0];
|
options.padding !== undefined ? options.padding : [0, 0, 0, 0];
|
||||||
@@ -1332,6 +1370,18 @@ class View extends BaseObject {
|
|||||||
* @param {import("./pixel.js").Pixel} position Position on the view to center on.
|
* @param {import("./pixel.js").Pixel} position Position on the view to center on.
|
||||||
*/
|
*/
|
||||||
centerOnInternal(coordinate, size, position) {
|
centerOnInternal(coordinate, size, position) {
|
||||||
|
this.setCenterInternal(
|
||||||
|
this.calculateShiftedCenter(coordinate, 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.
|
||||||
|
* @return {import("./coordinate.js").Coordinate} Shifted center.
|
||||||
|
*/
|
||||||
|
calculateShiftedCenter(coordinate, size, position) {
|
||||||
// calculate rotated position
|
// calculate rotated position
|
||||||
const rotation = this.getRotation();
|
const rotation = this.getRotation();
|
||||||
const cosAngle = Math.cos(-rotation);
|
const cosAngle = Math.cos(-rotation);
|
||||||
@@ -1347,7 +1397,7 @@ class View extends BaseObject {
|
|||||||
const centerX = rotX * cosAngle - rotY * sinAngle;
|
const centerX = rotX * cosAngle - rotY * sinAngle;
|
||||||
const centerY = rotY * cosAngle + rotX * sinAngle;
|
const centerY = rotY * cosAngle + rotX * sinAngle;
|
||||||
|
|
||||||
this.setCenterInternal([centerX, centerY]);
|
return [centerX, centerY];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1660,6 +1660,32 @@ describe('ol.View', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getViewportSizeMinusPadding_()', function () {
|
||||||
|
let map, target;
|
||||||
|
beforeEach(function () {
|
||||||
|
target = document.createElement('div');
|
||||||
|
target.style.width = '200px';
|
||||||
|
target.style.height = '150px';
|
||||||
|
document.body.appendChild(target);
|
||||||
|
map = new Map({
|
||||||
|
target: target,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
afterEach(function () {
|
||||||
|
map.setTarget(null);
|
||||||
|
document.body.removeChild(target);
|
||||||
|
});
|
||||||
|
it('same as getViewportSize_ when no padding is set', function () {
|
||||||
|
const size = map.getView().getViewportSizeMinusPadding_();
|
||||||
|
expect(size).to.eql(map.getView().getViewportSize_());
|
||||||
|
});
|
||||||
|
it('correctly updates when the padding is changed', function () {
|
||||||
|
map.getView().padding = [1, 2, 3, 4];
|
||||||
|
const size = map.getView().getViewportSizeMinusPadding_();
|
||||||
|
expect(size).to.eql([194, 146]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('fit', function () {
|
describe('fit', function () {
|
||||||
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
const originalRequestAnimationFrame = window.requestAnimationFrame;
|
||||||
const originalCancelAnimationFrame = window.cancelAnimationFrame;
|
const originalCancelAnimationFrame = window.cancelAnimationFrame;
|
||||||
@@ -1776,6 +1802,14 @@ describe('ol.View', function () {
|
|||||||
expect(view.getCenter()[0]).to.be(1500);
|
expect(view.getCenter()[0]).to.be(1500);
|
||||||
expect(view.getCenter()[1]).to.be(1500);
|
expect(view.getCenter()[1]).to.be(1500);
|
||||||
});
|
});
|
||||||
|
it('fits correctly to the extent when a padding is configured', function () {
|
||||||
|
view.padding = [100, 0, 0, 100];
|
||||||
|
view.setViewportSize([200, 200]);
|
||||||
|
view.fit([1000, 1000, 2000, 2000]);
|
||||||
|
expect(view.getResolution()).to.be(10);
|
||||||
|
expect(view.getCenter()[0]).to.be(1500);
|
||||||
|
expect(view.getCenter()[1]).to.be(1500);
|
||||||
|
});
|
||||||
it('fits correctly to the extent when a view extent is configured', function () {
|
it('fits correctly to the extent when a view extent is configured', function () {
|
||||||
view.options_.extent = [1500, 0, 2500, 10000];
|
view.options_.extent = [1500, 0, 2500, 10000];
|
||||||
view.applyOptions_(view.options_);
|
view.applyOptions_(view.options_);
|
||||||
@@ -2157,6 +2191,23 @@ describe('ol.View', function () {
|
|||||||
expect(center[1]).to.roughlyEqual(0, 1e-10);
|
expect(center[1]).to.roughlyEqual(0, 1e-10);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getState', function () {
|
||||||
|
let view;
|
||||||
|
beforeEach(function () {
|
||||||
|
view = new View({
|
||||||
|
center: [0, 0],
|
||||||
|
resolutions: [1],
|
||||||
|
zoom: 0,
|
||||||
|
});
|
||||||
|
view.setViewportSize([100, 100]);
|
||||||
|
});
|
||||||
|
it('Correctly shifts the viewport center when a padding is set', function () {
|
||||||
|
view.padding = [50, 0, 0, 50];
|
||||||
|
const state = view.getState();
|
||||||
|
expect(state.center).to.eql([-25, 25]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('does not start unexpected animations during interaction', function () {
|
describe('does not start unexpected animations during interaction', function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user