Merge pull request #10537 from mike-000/patch-3

Correct resolution used for scale bar and add dpi option
This commit is contained in:
Andreas Hocevar
2020-04-04 09:53:54 +02:00
committed by GitHub
5 changed files with 218 additions and 6 deletions

View File

@@ -5,6 +5,7 @@
"common": false,
"createMapboxStreetsV6Style": false,
"d3": false,
"domtoimage": false,
"geojsonvt": false,
"GyroNorm": false,
"jsPDF": false,

View File

@@ -0,0 +1,6 @@
.wrapper {
max-width: 566px;
width: 100%;
height: 400px;
overflow: hidden;
}

View File

@@ -0,0 +1,47 @@
---
layout: example.html
title: Print to scale example
shortdesc: Example of printing a map to a specified scale.
docs: >
Example of printing a map to a specified scale.
The print is exported as a PDF using the <a href="https://github.com/MrRio/jsPDF" target="_blank">jsPDF</a> library.
Unlike the <a href="export-pdf.html">Export PDF example</a> the on screen map is only used to set the center and rotation.
The extent printed depends on the scale and page size. To print the scale bar and attributions the example uses the
<a href="https://github.com/1904labs/dom-to-image-more" target="_blank">dom-to-image-more</a> library. Due to browser
limitations and restrictions <b>Internet Explorer and Safari are not supported</b>.
tags: "print, printing, scale, scaleline, export, pdf"
resources:
- https://unpkg.com/dom-to-image-more@2.8.0/dist/dom-to-image-more.min.js
- https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.5.3/jspdf.min.js
---
<div class="wrapper">
<div id="map" class="map"></div>
</div>
<form class="form">
<label>Page size </label>
<select id="format">
<option value="a0">A0 (slow)</option>
<option value="a1">A1</option>
<option value="a2">A2</option>
<option value="a3">A3</option>
<option value="a4" selected>A4</option>
<option value="a5">A5 (fast)</option>
</select>
<label>Resolution </label>
<select id="resolution">
<option value="72">72 dpi (fast)</option>
<option value="150">150 dpi</option>
<option value="200" selected>200 dpi</option>
<option value="300">300 dpi (slow)</option>
</select>
<label>Scale </label>
<select id="scale">
<option value="500">1:500000</option>
<option value="250" selected>1:250000</option>
<option value="100">1:100000</option>
<option value="50">1:50000</option>
<option value="25">1:25000</option>
<option value="10">1:10000</option>
</select>
</form>
<button id="export-pdf">Export PDF</button>

128
examples/print-to-scale.js Normal file
View File

@@ -0,0 +1,128 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import {defaults as defaultControls, ScaleLine} from '../src/ol/control.js';
import WMTSCapabilities from '../src/ol/format/WMTSCapabilities.js';
import TileLayer from '../src/ol/layer/Tile.js';
import {get as getProjection, getPointResolution} from '../src/ol/proj.js';
import {register} from '../src/ol/proj/proj4.js';
import WMTS, {optionsFromCapabilities} from '../src/ol/source/WMTS.js';
import proj4 from 'proj4';
proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' +
'+x_0=400000 +y_0=-100000 +ellps=airy ' +
'+towgs84=446.448,-125.157,542.06,0.15,0.247,0.842,-20.489 ' +
'+units=m +no_defs');
register(proj4);
const proj27700 = getProjection('EPSG:27700');
proj27700.setExtent([0, 0, 700000, 1300000]);
const raster = new TileLayer();
const url = 'https://tiles.arcgis.com/tiles/qHLhLQrcvEnxjtPr/arcgis/rest/services/OS_Open_Raster/MapServer/WMTS';
fetch(url)
.then(function(response) {
return response.text();
})
.then(function(text) {
const result = new WMTSCapabilities().read(text);
const options = optionsFromCapabilities(result, {
layer: 'OS_Open_Raster'
});
options.attributions = 'Contains OS data © Crown Copyright and database right ' + new Date().getFullYear();
options.crossOrigin = '';
options.projection = proj27700;
options.wrapX = false;
raster.setSource(new WMTS(options));
});
const map = new Map({
layers: [raster],
controls: defaultControls({
attributionOptions: {collapsible: false}
}),
target: 'map',
view: new View({
center: [373500, 436500],
projection: proj27700,
zoom: 7
})
});
const scaleLine = new ScaleLine({bar: true, text: true, minWidth: 125});
map.addControl(scaleLine);
const dims = {
a0: [1189, 841],
a1: [841, 594],
a2: [594, 420],
a3: [420, 297],
a4: [297, 210],
a5: [210, 148]
};
// export options for html-to-image.
// See: https://github.com/bubkoo/html-to-image#options
const exportOptions = {
filter: function(element) {
const className = element.className || '';
return (
className.indexOf('ol-control') === -1 ||
className.indexOf('ol-scale') > -1 ||
(className.indexOf('ol-attribution') > -1 &&
className.indexOf('ol-uncollapsible'))
);
}
};
const exportButton = document.getElementById('export-pdf');
exportButton.addEventListener('click', function() {
exportButton.disabled = true;
document.body.style.cursor = 'progress';
const format = document.getElementById('format').value;
const resolution = document.getElementById('resolution').value;
const scale = document.getElementById('scale').value;
const dim = dims[format];
const width = Math.round(dim[0] * resolution / 25.4);
const height = Math.round(dim[1] * resolution / 25.4);
const viewResolution = map.getView().getResolution();
const scaleResolution = scale / getPointResolution(
map.getView().getProjection(),
resolution / 25.4,
map.getView().getCenter()
);
map.once('rendercomplete', function() {
exportOptions.width = width;
exportOptions.height = height;
domtoimage.toJpeg(map.getViewport(), exportOptions).then(function(dataUrl) {
const pdf = new jsPDF('landscape', undefined, format);
pdf.addImage(dataUrl, 'JPEG', 0, 0, dim[0], dim[1]);
pdf.save('map.pdf');
// Reset original map size
scaleLine.setDpi();
map.getTargetElement().style.width = '';
map.getTargetElement().style.height = '';
map.updateSize();
map.getView().setResolution(viewResolution);
exportButton.disabled = false;
document.body.style.cursor = 'auto';
});
});
// Set print size
scaleLine.setDpi(resolution);
map.getTargetElement().style.width = width + 'px';
map.getTargetElement().style.height = height + 'px';
map.updateSize();
map.getView().setResolution(scaleResolution);
}, false);

View File

@@ -34,11 +34,18 @@ export const Units = {
*/
const LEADING_DIGITS = [1, 2, 5];
/**
* @const
* @type {number}
*/
const DEFAULT_DPI = 25.4 / 0.28;
/**
* @typedef {Object} Options
* @property {string} [className='ol-scale-line'] CSS Class name.
* @property {number} [minWidth=64] Minimum width in pixels.
* @property {number} [minWidth=64] Minimum width in pixels at the OGC default dpi. The width will be
* adjusted to match the dpi used.
* @property {function(import("../MapEvent.js").default)} [render] Function called when the control
* should be re-rendered. This is called in a `requestAnimationFrame` callback.
* @property {HTMLElement|string} [target] Specify a target if you want the control
@@ -49,6 +56,8 @@ const LEADING_DIGITS = [1, 2, 5];
* for best results. Only applies when `bar` is `true`.
* @property {boolean} [text=false] Render the text scale above of the scalebar. Only applies
* when `bar` is `true`.
* @property {number|undefined} [dpi=undefined] dpi of output device such as printer. Only applies
* when `bar` is `true`. If undefined the OGC default screen pixel size of 0.28mm will be assumed.
*/
@@ -146,6 +155,12 @@ class ScaleLine extends Control {
*/
this.scaleBarText_ = options.text || false;
/**
* @private
* @type {number|undefined}
*/
this.dpi_ = options.dpi || undefined;
}
/**
@@ -176,6 +191,15 @@ class ScaleLine extends Control {
this.set(UNITS_PROP, units);
}
/**
* Specify the dpi of output device such as printer.
* @param {number|undefined} dpi The dpi of output device.
* @api
*/
setDpi(dpi) {
this.dpi_ = dpi;
}
/**
* @private
*/
@@ -199,7 +223,9 @@ class ScaleLine extends Control {
let pointResolution =
getPointResolution(projection, viewState.resolution, center, pointResolutionUnits);
let nominalCount = this.minWidth_ * pointResolution;
const minWidth = this.minWidth_ * (this.dpi_ || DEFAULT_DPI) / DEFAULT_DPI;
let nominalCount = minWidth * pointResolution;
let suffix = '';
if (units == Units.DEGREES) {
const metersPerDegree = METERS_PER_UNIT[ProjUnits.DEGREES];
@@ -256,7 +282,7 @@ class ScaleLine extends Control {
}
let i = 3 * Math.floor(
Math.log(this.minWidth_ * pointResolution) / Math.log(10));
Math.log(minWidth * pointResolution) / Math.log(10));
let count, width, decimalCount;
while (true) {
decimalCount = Math.floor(i / 3);
@@ -267,7 +293,7 @@ class ScaleLine extends Control {
this.element.style.display = 'none';
this.renderedVisible_ = false;
return;
} else if (width >= this.minWidth_) {
} else if (width >= minWidth) {
break;
}
++i;
@@ -406,8 +432,12 @@ class ScaleLine extends Control {
* @return {number} The appropriate scale.
*/
getScaleForResolution() {
const resolution = this.getMap().getView().getResolution();
const dpi = 25.4 / 0.28;
const resolution = getPointResolution(
this.viewState_.projection,
this.viewState_.resolution,
this.viewState_.center
);
const dpi = this.dpi_ || DEFAULT_DPI;
const mpu = this.viewState_.projection.getMetersPerUnit();
const inchesPerMeter = 39.37;
return parseFloat(resolution.toString()) * mpu * inchesPerMeter * dpi;