From e2ac566c05c6670704b577c316998e9dbf4f4c4f Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Thu, 16 Jan 2020 23:41:52 +0000 Subject: [PATCH 1/8] Correct resolution used for scale. Add dpi option. Also add setDpi and setMinWidth methods Add Print to scale example --- examples/print-to-scale.css | 6 ++ examples/print-to-scale.html | 44 ++++++++++++ examples/print-to-scale.js | 132 +++++++++++++++++++++++++++++++++++ src/ol/control/ScaleLine.js | 34 ++++++++- 4 files changed, 214 insertions(+), 2 deletions(-) create mode 100644 examples/print-to-scale.css create mode 100644 examples/print-to-scale.html create mode 100644 examples/print-to-scale.js diff --git a/examples/print-to-scale.css b/examples/print-to-scale.css new file mode 100644 index 0000000000..383a21eb6b --- /dev/null +++ b/examples/print-to-scale.css @@ -0,0 +1,6 @@ +.container { + 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..7f4068e41a --- /dev/null +++ b/examples/print-to-scale.html @@ -0,0 +1,44 @@ +--- +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. +tags: "print, printing, scale, scaleline, export, pdf" +resources: + - https://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js +--- +
+
+
+
+ + + + + + +
+ diff --git a/examples/print-to-scale.js b/examples/print-to-scale.js new file mode 100644 index 0000000000..80e8e30e4d --- /dev/null +++ b/examples/print-to-scale.js @@ -0,0 +1,132 @@ +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'; + +import {toJpeg} from 'html-to-image'; + + +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}); +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; + 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(); + scaleLine.setMinWidth(); + 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); + scaleLine.setMinWidth(resolution * 4 / 2.54); // 4cm + 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..be645ea4f9 100644 --- a/src/ol/control/ScaleLine.js +++ b/src/ol/control/ScaleLine.js @@ -49,6 +49,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 +148,12 @@ class ScaleLine extends Control { */ this.scaleBarText_ = options.text || false; + /** + * @private + * @type {number|undefined} + */ + this.dpi_ = options.dpi || undefined; + } /** @@ -176,6 +184,24 @@ 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; + } + + /** + * Set the minimum width. + * @param {number|undefined} minWidth The ninimum width in pixels. + * @api + */ + setMinWidth(minWidth) { + this.minWidth_ = minWidth !== undefined ? minWidth : 64; + } + /** * @private */ @@ -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_ || (25.4 / 0.28); const mpu = this.viewState_.projection.getMetersPerUnit(); const inchesPerMeter = 39.37; return parseFloat(resolution.toString()) * mpu * inchesPerMeter * dpi; From e6658aec1eed84b202a374eb787bdc4a1f0218e7 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Thu, 2 Apr 2020 13:47:46 +0100 Subject: [PATCH 2/8] add domtoimage --- examples/.eslintrc | 1 + 1 file changed, 1 insertion(+) 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, From b56ca954d51ef05ff3103cc1252dac91ee87bc51 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Thu, 2 Apr 2020 13:55:17 +0100 Subject: [PATCH 3/8] avoid classname conflict with example template --- examples/print-to-scale.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/print-to-scale.css b/examples/print-to-scale.css index 383a21eb6b..7911e178ab 100644 --- a/examples/print-to-scale.css +++ b/examples/print-to-scale.css @@ -1,4 +1,4 @@ -.container { +.wrapper { max-width: 566px; width: 100%; height: 400px; From ab006abf25b770e938aa99da8cd23eda2bf67477 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Thu, 2 Apr 2020 14:16:51 +0100 Subject: [PATCH 4/8] use dom-to-image-more --- examples/print-to-scale.html | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/print-to-scale.html b/examples/print-to-scale.html index 7f4068e41a..54d88db427 100644 --- a/examples/print-to-scale.html +++ b/examples/print-to-scale.html @@ -6,12 +6,15 @@ 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. + 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://cdnjs.cloudflare.com/ajax/libs/jspdf/1.2.61/jspdf.min.js + - 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 --- -
+
From 23fe5463faf5e4112148edea4fe5d3c36eee53dc Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Thu, 2 Apr 2020 14:20:31 +0100 Subject: [PATCH 5/8] use dom-to-image-more --- examples/print-to-scale.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/print-to-scale.js b/examples/print-to-scale.js index 80e8e30e4d..01cda5568a 100644 --- a/examples/print-to-scale.js +++ b/examples/print-to-scale.js @@ -8,8 +8,6 @@ import {register} from '../src/ol/proj/proj4.js'; import WMTS, {optionsFromCapabilities} from '../src/ol/source/WMTS.js'; import proj4 from 'proj4'; -import {toJpeg} from 'html-to-image'; - proj4.defs('EPSG:27700', '+proj=tmerc +lat_0=49 +lon_0=-2 +k=0.9996012717 ' + '+x_0=400000 +y_0=-100000 +ellps=airy ' + @@ -105,7 +103,7 @@ exportButton.addEventListener('click', function() { map.once('rendercomplete', function() { exportOptions.width = width; exportOptions.height = height; - toJpeg(map.getViewport(), exportOptions).then(function(dataUrl) { + 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'); From 6006fb2c0336a813420580c8b230d8b514bb2b72 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Fri, 3 Apr 2020 17:11:20 +0100 Subject: [PATCH 6/8] base minWidth on default dpi & remove setMinWidth --- examples/print-to-scale.js | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/examples/print-to-scale.js b/examples/print-to-scale.js index 01cda5568a..ec8ef2ae23 100644 --- a/examples/print-to-scale.js +++ b/examples/print-to-scale.js @@ -52,7 +52,7 @@ const map = new Map({ }) }); -const scaleLine = new ScaleLine({bar: true, text: true}); +const scaleLine = new ScaleLine({bar: true, text: true, minWidth: 125}); map.addControl(scaleLine); @@ -109,7 +109,6 @@ exportButton.addEventListener('click', function() { pdf.save('map.pdf'); // Reset original map size scaleLine.setDpi(); - scaleLine.setMinWidth(); map.getTargetElement().style.width = ''; map.getTargetElement().style.height = ''; map.updateSize(); @@ -121,7 +120,6 @@ exportButton.addEventListener('click', function() { // Set print size scaleLine.setDpi(resolution); - scaleLine.setMinWidth(resolution * 4 / 2.54); // 4cm map.getTargetElement().style.width = width + 'px'; map.getTargetElement().style.height = height + 'px'; map.updateSize(); From 6aa953b5713c591393b5b657c7026d5244572ad4 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Fri, 3 Apr 2020 17:11:25 +0100 Subject: [PATCH 7/8] base minWidth on default dpi & remove setMinWidth --- src/ol/control/ScaleLine.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/ol/control/ScaleLine.js b/src/ol/control/ScaleLine.js index be645ea4f9..5aab52706c 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 @@ -193,15 +200,6 @@ class ScaleLine extends Control { this.dpi_ = dpi; } - /** - * Set the minimum width. - * @param {number|undefined} minWidth The ninimum width in pixels. - * @api - */ - setMinWidth(minWidth) { - this.minWidth_ = minWidth !== undefined ? minWidth : 64; - } - /** * @private */ @@ -225,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]; @@ -282,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); @@ -293,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; From fb8442641199d5cba0831fc0856a181a78301463 Mon Sep 17 00:00:00 2001 From: mike-000 <49240900+mike-000@users.noreply.github.com> Date: Fri, 3 Apr 2020 17:17:11 +0100 Subject: [PATCH 8/8] use constant for default dpi --- src/ol/control/ScaleLine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ol/control/ScaleLine.js b/src/ol/control/ScaleLine.js index 5aab52706c..b910182ab1 100644 --- a/src/ol/control/ScaleLine.js +++ b/src/ol/control/ScaleLine.js @@ -437,7 +437,7 @@ class ScaleLine extends Control { this.viewState_.resolution, this.viewState_.center ); - const dpi = this.dpi_ || (25.4 / 0.28); + const dpi = this.dpi_ || DEFAULT_DPI; const mpu = this.viewState_.projection.getMetersPerUnit(); const inchesPerMeter = 39.37; return parseFloat(resolution.toString()) * mpu * inchesPerMeter * dpi;