diff --git a/examples/.eslintrc b/examples/.eslintrc index 769357d35a..c404b2e6e6 100644 --- a/examples/.eslintrc +++ b/examples/.eslintrc @@ -5,6 +5,7 @@ "common": false, "createMapboxStreetsV6Style": false, "d3": false, + "domtoimage": false, "geojsonvt": false, "GyroNorm": false, "jsPDF": false, diff --git a/examples/print-to-scale.css b/examples/print-to-scale.css new file mode 100644 index 0000000000..7911e178ab --- /dev/null +++ b/examples/print-to-scale.css @@ -0,0 +1,6 @@ +.wrapper { + max-width: 566px; + width: 100%; + height: 400px; + overflow: hidden; +} diff --git a/examples/print-to-scale.html b/examples/print-to-scale.html new file mode 100644 index 0000000000..54d88db427 --- /dev/null +++ b/examples/print-to-scale.html @@ -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 jsPDF library. + Unlike the Export PDF example 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 + dom-to-image-more library. Due to browser + limitations and restrictions Internet Explorer and Safari are not supported. +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 +--- +
+
+
+
+ + + + + + +
+ diff --git a/examples/print-to-scale.js b/examples/print-to-scale.js new file mode 100644 index 0000000000..ec8ef2ae23 --- /dev/null +++ b/examples/print-to-scale.js @@ -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); diff --git a/src/ol/control/ScaleLine.js b/src/ol/control/ScaleLine.js index e0d7733ea3..b910182ab1 100644 --- a/src/ol/control/ScaleLine.js +++ b/src/ol/control/ScaleLine.js @@ -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;