UNPKG

react-spatial

Version:

Components to build React map apps.

502 lines (443 loc) 15.1 kB
import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import OLMap from 'ol/Map'; import { getTopLeft, getBottomRight } from 'ol/extent'; import Button from '../Button'; import NorthArrowSimple from '../../images/northArrow.url.svg'; import NorthArrowCircle from '../../images/northArrowCircle.url.svg'; var propTypes = { /** * Title of the button. */ title: PropTypes.string.isRequired, /** * Children content of the button. */ children: PropTypes.node.isRequired, /** * CSS class of the button. */ className: PropTypes.string, /** * HTML tabIndex attribute */ tabIndex: PropTypes.number, /** * HTML disabled attribute */ disabled: PropTypes.bool, /** * Format to save the image. */ saveFormat: PropTypes.oneOf(['image/jpeg', 'image/png']), /** An existing [ol/Map](https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html). */ map: PropTypes.instanceOf(OLMap), /** * Extent for the export. If no extent is given, the whole map is exported. */ extent: PropTypes.arrayOf(PropTypes.number), /** * Array of 4 [ol/Coordinate](https://openlayers.org/en/latest/apidoc/module-ol_coordinate.html#~Coordinate). * If no coordinates and no extent are given, the whole map is exported. * This property must be used to export rotated map. * If you don't need to export rotated map the extent property can be used as well. * If extent is specified, coordinates property is ignored. */ coordinates: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.number)), /** * Scale the map for better quality. Possible values: 1, 2 or 3. * WARNING: The tiled layer with a WMTS or XYZ source must provides an url * for each scale in the config file. */ scale: PropTypes.number, /** * Function called before the dowload process begins. */ onSaveStart: PropTypes.func, /** * Function called after the dowload process ends. * * @param {object} error Error message the process fails. */ onSaveEnd: PropTypes.func, /** * Extra data, such as copyright, north arrow configuration. * All extra data is optional. * * Example 1: * { copyright: { text: 'Example copyright', // Copyright text or function font: '10px Arial', // Font, default is '12px Arial' fillStyle: 'blue', // Fill style, default is 'black' }, northArrow, // True if the north arrow // should be placed with default configuration // (default image, rotation=0, circled=False) } * Example 2: * { northArrow: { src: NorthArrowCustom, width: 60, // Width in px, default is 80 height: 100, // Height in px, default is 80 rotation: 25, // Absolute rotation in degrees as number or function } } * Example 3: * { copyright: { text: () => { // Copyright as function return this.getCopyright(); }, }, northArrow: { rotation: () => { // Rotation as function return NorthArrow.radToDeg(this.map.getView().getRotation()); }, circled, // Display circle around the north arrow (Does not work for custom src) }, } */ extraData: PropTypes.object, }; var defaultProps = { map: null, tabIndex: 0, className: 'tm-canvas-save-button tm-button', saveFormat: 'image/png', extent: null, extraData: null, coordinates: null, scale: 1, disabled: undefined, onSaveStart: function (map) { return Promise.resolve(map); }, onSaveEnd: function () {}, }; /** * This component displays a button to save canvas as an image. */ var CanvasSaveButton = /*@__PURE__*/(function (PureComponent) { function CanvasSaveButton(props) { PureComponent.call(this, props); var ref = this.props; var saveFormat = ref.saveFormat; this.options = { format: saveFormat, }; this.fileExt = this.options.format === 'image/jpeg' ? 'jpg' : 'png'; this.padding = 5; } if ( PureComponent ) CanvasSaveButton.__proto__ = PureComponent; CanvasSaveButton.prototype = Object.create( PureComponent && PureComponent.prototype ); CanvasSaveButton.prototype.constructor = CanvasSaveButton; CanvasSaveButton.prototype.getDownloadImageName = function getDownloadImageName () { return ( "" + (window.document.title.replace(/ /g, '_').toLowerCase()) + "." + (this.fileExt) ); }; // Ensure the font size fita with the image width. CanvasSaveButton.prototype.decreaseFontSize = function decreaseFontSize (destContext, maxWidth, copyright, scale) { var minFontSize = 8; var sizeMatch; var fontSize; do { sizeMatch = destContext.font.match(/[0-9]+(?:\.[0-9]+)?(px)/i); fontSize = parseInt(sizeMatch[0].replace(sizeMatch[1], ''), 10); // eslint-disable-next-line no-param-reassign destContext.font = destContext.font.replace(fontSize, fontSize - 1); if (fontSize - 1 === minFontSize) { this.multilineCopyright = true; } } while ( fontSize - 1 > minFontSize && destContext.measureText(copyright).width * scale > maxWidth ); return destContext.font; }; // If minimal fontsize is reached, divide copyright in two lines. CanvasSaveButton.prototype.splitCopyrightLine = function splitCopyrightLine (destContext, destCanvas, maxWidth, copyright, scale) { var newCopyright = copyright; var wordNumber = copyright.split(' ').length; for (var i = 0; i < wordNumber; i += 1) { newCopyright = newCopyright.substring(0, newCopyright.lastIndexOf(' ')); // Stop removing word when fits within one line. if (destContext.measureText(newCopyright).width * scale < maxWidth) { break; } } // Draw fist line (line break isn't supported for fillText). destContext.fillText( newCopyright, this.padding, destCanvas.height / scale - 3 * this.padding ); // Draw second line. destContext.fillText( copyright.replace(newCopyright, ''), this.padding, destCanvas.height / scale - this.padding ); }; CanvasSaveButton.prototype.drawCopyright = function drawCopyright (destContext, destCanvas, maxWidth) { var ref = this.props; var extraData = ref.extraData; var scale = ref.scale; var ref$1 = extraData.copyright; var text = ref$1.text; var font = ref$1.font; var fillStyle = ref$1.fillStyle; var copyright = typeof text === 'function' ? text() : text; this.multilineCopyright = false; destContext.save(); destContext.scale(scale, scale); // eslint-disable-next-line no-param-reassign destContext.font = font || '12px Arial'; // eslint-disable-next-line no-param-reassign destContext.fillStyle = fillStyle || 'black'; this.decreaseFontSize(destContext, maxWidth, copyright, scale); if (this.multilineCopyright) { this.splitCopyrightLine( destContext, destCanvas, maxWidth, copyright, scale ); } else { destContext.fillText( copyright, this.padding, destCanvas.height / scale - this.padding ); } destContext.restore(); }; CanvasSaveButton.prototype.drawNorthArrow = function drawNorthArrow (destContext, destCanvas) { var this$1 = this; var ref = this.props; var scale = ref.scale; var extraData = ref.extraData; var ref$1 = extraData.northArrow; var src = ref$1.src; var circled = ref$1.circled; var width = ref$1.width; var height = ref$1.height; var rotation = ref$1.rotation; return new Promise(function (resolve) { var img = new Image(); img.crossOrigin = 'Anonymous'; img.src = src || (circled ? NorthArrowCircle : NorthArrowSimple); img.onload = function () { destContext.save(); var arrowWidth = (width || 80) * scale; var arrowHeight = (height || 80) * scale; destContext.translate( destCanvas.width - 2 * this$1.padding - arrowWidth / 2, destCanvas.height - 2 * this$1.padding - arrowHeight / 2 ); if (rotation) { var angle = typeof rotation === 'function' ? rotation() : rotation; destContext.rotate(angle * (Math.PI / 180)); } destContext.drawImage( img, -arrowWidth / 2, -arrowHeight / 2, arrowWidth, arrowHeight ); destContext.restore(); // Return the pixels width of the arrow and the margin right, // that must not be occupied by the copyright. resolve(arrowWidth + 2 * this$1.padding); }; img.onerror = function () { resolve(); }; }); }; CanvasSaveButton.prototype.calculatePixelsToExport = function calculatePixelsToExport (mapToExport) { var assign; var ref = this.props; var extent = ref.extent; var coordinates = ref.coordinates; var firstCoordinate; var oppositeCoordinate; if (extent) { firstCoordinate = getTopLeft(extent); oppositeCoordinate = getBottomRight(extent); } else if (coordinates) { // In case of coordinates coming from DragBox interaction: // firstCoordinate is the first coordinate drawn by the user. // oppositeCoordinate is the coordinate of the point dragged by the user. (assign = coordinates, firstCoordinate = assign[0], oppositeCoordinate = assign[2]); } if (firstCoordinate && oppositeCoordinate) { var firstPixel = mapToExport.getPixelFromCoordinate(firstCoordinate); var oppositePixel = mapToExport.getPixelFromCoordinate( oppositeCoordinate ); var pixelTopLeft = [ firstPixel[0] <= oppositePixel[0] ? firstPixel[0] : oppositePixel[0], firstPixel[1] <= oppositePixel[1] ? firstPixel[1] : oppositePixel[1] ]; var pixelBottomRight = [ firstPixel[0] > oppositePixel[0] ? firstPixel[0] : oppositePixel[0], firstPixel[1] > oppositePixel[1] ? firstPixel[1] : oppositePixel[1] ]; return { x: pixelTopLeft[0], y: pixelTopLeft[1], w: pixelBottomRight[0] - pixelTopLeft[0], h: pixelBottomRight[1] - pixelTopLeft[1], }; } return null; }; CanvasSaveButton.prototype.createCanvasImage = function createCanvasImage (mapToExport) { var this$1 = this; var ref = this.props; var extraData = ref.extraData; return new Promise(function (resolve) { mapToExport.once('rendercomplete', function () { // Find all layer canvases and add it to dest canvas. var canvases = mapToExport .getTargetElement() .getElementsByTagName('canvas'); // Create the canvas to export with the good size. var destCanvas; var destContext; canvases.forEach(function (canvas) { if (!canvas.width || !canvas.height) { return; } var clip = this$1.calculatePixelsToExport(mapToExport) || { x: 0, y: 0, w: canvas.width, h: canvas.height, }; if (!destCanvas) { destCanvas = document.createElement('canvas'); destCanvas.width = clip.w; destCanvas.height = clip.h; destContext = destCanvas.getContext('2d'); } // Draw canvas to the canvas to export. destContext.drawImage( canvas, clip.x, clip.y, clip.w, clip.h, 0, 0, destCanvas.width, destCanvas.height ); }); // North arrow var p = Promise.resolve(); if (destContext && extraData && extraData.northArrow) { p = this$1.drawNorthArrow(destContext, destCanvas); } p.then(function (arrowWidth) { // Copyright if ( destContext && extraData && extraData.copyright && extraData.copyright.text ) { var maxWidth = arrowWidth ? destContext.canvas.width - arrowWidth : destContext.canvas.width; this$1.drawCopyright(destContext, destCanvas, maxWidth); } resolve(destCanvas); }); }); mapToExport.renderSync(); }); }; CanvasSaveButton.prototype.downloadCanvasImage = function downloadCanvasImage (canvas) { var this$1 = this; if (/msie (9|10)/gi.test(window.navigator.userAgent.toLowerCase())) { // ie 9 and 10 var url = canvas.toDataURL(this.options.format); var w = window.open('about:blank', ''); w.document.write(("<img src=\"" + url + "\" alt=\"from canvas\"/>")); } else if (window.navigator.msSaveBlob) { // ie 11 and higher var image; try { image = canvas.msToBlob(); } catch (e) { // eslint-disable-next-line no-console console.log(e); } window.navigator.msSaveBlob( new Blob([image], { type: this.options.format, }), this.getDownloadImageName() ); } else { // Use blob for large images canvas.toBlob(function (blob) { var link = document.createElement('a'); link.download = this$1.getDownloadImageName(); link.href = URL.createObjectURL(blob); // append child to document for firefox to be able to download. document.body.appendChild(link); link.click(); }, this.options.format); } }; CanvasSaveButton.prototype.render = function render () { var this$1 = this; var ref = this.props; var title = ref.title; var map = ref.map; var children = ref.children; var tabIndex = ref.tabIndex; var className = ref.className; var disabled = ref.disabled; var onSaveStart = ref.onSaveStart; var onSaveEnd = ref.onSaveEnd; return ( React.createElement( Button, { className: className, title: title, tabIndex: tabIndex, disabled: disabled, onClick: function (e) { if (window.navigator.msSaveBlob) { // ie only e.preventDefault(); e.stopPropagation(); } onSaveStart(map).then(function (mapToExport) { return this$1.createCanvasImage(mapToExport || map) .then(function (canvas) { this$1.downloadCanvasImage(canvas); onSaveEnd(mapToExport); }) .catch(function (err) { if (err) { // eslint-disable-next-line no-console console.error(err); } onSaveEnd(mapToExport, err); }); }); } }, children ) ); }; return CanvasSaveButton; }(PureComponent)); CanvasSaveButton.propTypes = propTypes; CanvasSaveButton.defaultProps = defaultProps; export default CanvasSaveButton; //# sourceMappingURL=CanvasSaveButton.js.map